-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change "at least" to "at most" #1875
Conversation
Closed, since this is a duplicate of #1782. |
Reopening, since #1782 doesn't seem to be moving. |
The returned value must not last longer than the arguments. If they are both bounded from below, using "at least", then the implication is that the returned value can last longer than the arguments, which is precisely what must not happen. Signed-off-by: mulhern <amulhern@redhat.com>
5705076
to
103b42b
Compare
rebased. |
No, I don't think this is correct. An "at most" lifetime would be useless, because it wouldn't tell you anything about when it is safe to use. Something which was immediately dead would satisfy any "at most" lifetime. It really is "at least" -- that example From the perspective of the borrow checker, both |
@cuviper You're right that, if if the only constraint were that the lifetime must bound it from above, the value could be immediately dead. But the other constraint is that its use bounds it from below. They both must be satisfied, and that's where errors can come in.
I think that if the return type is 'static, that means that the lifetime constraint is simply being ignored and does not enter here. It's only if the parameter and the return type are both bound by the lifetime, either explicitly or implicitly, that there is a constraint. Try the following:
There are lots of choices for lifetime 'a. One choice is just the line
If I change the return type of
That sounds right. |
No, you don't have the power to "extend" the lifetime like this. Saying "at least" when we return |
Consider a different function then, with no inputs: fn get<'a>() -> &'a str { "foo" } This function has to return something that lives for at least In fact, the only thing we can logically return in this case is a |
One more: fn choose<'a, 'b: 'a, T>(a: &'a T, b: &'b T) -> &'a T {
if flip_a_coin() { a } else { b }
} We can return either reference, because But if we had written it the other way, |
@cuviper You're definitely getting my meaning wrong here. I thought of a new formulation that I like better: " |
Who is "you" in this scenario, the caller or the callee? When the caller provides a parameter with lifetime When the callee (the implementor of the function) receives a parameter with lifetime From either perspective, the lifetimes are still always "at least". I suppose the "at most" might come in where I said, "but can't use it any longer than that." It lives at least that long, but it's unknown whether it could actually live longer, so that's the most you can use it. |
I think that here, I'ld say, that 'a is not bounded from above, because it is not bound to a parameter, just to the return value. So, it must be big enough for all uses of the return type, and that is the only constraint. Such a constraint ought always to be satisfiable. To implement a function like that you can't just make a String and return a reference, because you'll get a borrowing error in the implementation of the function, but a Box seems to compile: |
That makes |
The caller.
Notice how 'a sneakily became an upper bound on the return value in your paragraph above. "but can't use it any longer than that". <-- SNIP -->
Aah, yeah, you noticed that too. I really think this is an existence property. When I write the code that calls the function and expect it to pass the borrow checker, I'm saying, to the compiler: "There exists some lifetime that does not exceed the scope of the parameter, and encompasses all the uses of the return value. Please find any lifetime that fullfills those requirements and trouble me no further." |
OK, I'm not sure where to go from here. I still think the original line is entirely correct:
Changing that to "at most" is incorrect, because the function really is required to fulfill |
It's kind of like how clamping a minimum value uses the |
I don't mind if this is closed. I got what I wanted, which was a stimulating discussion about lifetimes, which should go some way to solidifying my understanding, and making me more facile with the tricky stuff. So, thanks! I'll still think the wording is incorrect and misleading. But I also feel that the Rust book is always going to struggle trying to find the balance between chatty and friendly on the one hand, and precisely correct on the other. As a former academic, I lean way toward the precisely correct side. So in future I'm going to confine myself to PRs and issues regarding the not-so-chatty-and-friendly Rust docs, like the man pages and the compiler error explanations. And that will be perfectly satisfying to me. |
These are just notes to self, really. I realized that thinking about these things would be easier if the names weren't overloaded, so I'm going to throw in a few names for the arguments to the function, When I write the code and call the function
Also, there is an implicit lower bound on both lifetimes, which is that they must be at least as large as the call site. If I were the compiler, taxed with finding these two lifetimes, I would find any that satisfy 'a's requirements first. Then I would see if there was any one of these possible 'a lifetimes that was contained within the scope of Q. If I find one, then I've found a valid 'b lifetime, and I'm done. If the return type were bounded by 'b, then I'ld be saying to the compiler:
As the compiler, I would first look for possible lifetimes to satisfy 'b's requirements. Then, I would be done, because the constraints on 'a are both from above, so I could maintain that A reasonable and compilable function can be constructed w/ either lifetime signature. But the lifetimes do constrain the implementation of the function. If the return type is bound by 'a, then either argument can be returned, but if by 'b, only q. This feels like one of those things that on the face of it is is so obvious that it is way too hard to explain, so I won't try. I'm more and more confident that this is a correct formulation, but I'm also aware that this is far too technical and mathematical to be appropriate for the book. Closing... |
FWIW I feel like @mulkieran is correct here. It seems that @cuviper is looking at The function signature is mostly of interest to the caller. It tells you that the variable which receives the return value must have a lifetime at most I believe the sentence is confusing because it is talking in terms of constraints that the implementer of the function has:
Again, this is a weird thing to explain and doesn't help understand what the borrow checker does to verify that the return value is not used beyond where it is allowed to. |
I stand by my position even from the perspective of the caller. The alternative, if you tell the caller that "this return value lives for at most For the implementor, "at least For the caller, when we say it lives "at least |
I genuinely have trouble understanding what you are getting at here... As a caller, you are only worried about how long you can hold on to the return value. You can't possibly have a lifetime that is too short, only one that is too long. let x = shortest("foo", "bazz");
// hmmm is x's lifetime to short?
// lets make it a bit longer...
// and a bit more...
// ok, might be safe to use now, lifetime seems long enough
Well, in that case, all confusion is cleared... I still feel that |
The caller isn't really responsible for the lifetime of the reference -- that was The caller also has their own borrow checking, but that's a little different. Consider: let x;
{
let foo = String::from("foo");
let bazz = String::from("bazz");
x = shortest(&foo, &bazz);
// 'a is limited by the input parameters
// x can still be used here
dbg!(x);
}
dbg!(x); // here is an error that foo and bazz don't live long enough |
I think part of the misunderstanding was that I think about lifetimes in this order:
For example, here I would think to myself:
|
After sleeping on it, I think I now better understand what is meant by "the return value lives at least as long as lifetime 'a". What it really means is "the return value lives at least as long as lifetime 'a (but we are not sure it lives beyond, so we can't assume it does)". For me, this was what |
I think I get the disconnect -- you see "the return value lives at least as long" and you're connecting that to The specific quote targeted by this PR does talk about the data rather than the "return value" though -- "The function signature also tells Rust that the string slice returned from the function will live at least as long as lifetime |
Yes, that's exactly what confused me:
In this sentence, |
Note that this is related somehow to #1901.