6 ms·
In that case, how does the caller know that the results will last as long as the input? Does it assume `'static` or is there some cleverness?
by milliams 6y ago
In that case, how does the caller know that the results will last as long as the input? Does it assume `'static` or is there some cleverness?
- deleted 6y ago[deleted]
- jen20 6y agoThis is determined by lifetime elision [1]. Specifically (quoting): - Each elided lifetime in the parameters becomes a distinct lifetime parameter. - If there is exactly one lifetime used in the parameters (elided or not), that lifetime is assigned to all elided output lifetimes. So, Since the signature has one `&str` as a parameter, the lifetime is assigned to the output lifetime also per these rules. [1]: https://doc.rust-lang.org/reference/lifetime-elision.html https://doc.rust-lang.org/reference/lifetime-elision.html (Edited to improve formatting)
- brundolf 6y agoAhh. So if I added a second parameter it would require the explicit lifetime? That may be what I was doing before that made me think I needed to be explicit all the time.
- jen20 6y agoYes - if there are two references as parameters, there is no way for the compiler to decide which the output reference may be borrowed from - and so which lifetime to assign the output, so it must be explicitly indicated by the programmer. There are a few a few example cases in the doc linked about that walk through the conditions it's necessary to add explicit lifetime annotation.
- jcranmer 6y agoI don't know the exact rules, but in general: fn foo(bar: &x) -> &y is inferred to be: fn foo<'a>(bar: &'a x) -> &'a y
- johnisgood 6y ago> I don't know the exact rules This pretty much sums it up for me. You need to know so many rules... you need to know what those symbols mean... when people refer to Perl's syntax that they frown upon, they are referring to this: heavy use of symbols whose meanings must be memorized. I cannot just look at the code and understand it as I can with say, Ada. Pretty frustrating. I do not have to learn Ada to know what the code does. Funny that. :D At times Rust code looks very similar to Perl and Haskell. Especially Perl.
- deleted 6y ago[deleted]
- steveklabnik 6y agoYeah I mean, it really depends on your perspective. The compiler has your back; there's no need to memorize the rules, because that's what it's there for. Forget the rules and don't write a lifetime where you needed one? You get this: error[E0106]: missing lifetime specifier --> src/lib.rs:1:29 | 1 | fn foo(x: &i32, y: &i32) -> &i32 { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` help: consider introducing a named lifetime parameter | 1 | fn foo<'a>(x: &'a i32, y: &'a i32) -> &'a i32 { | ^^^^ ^^^^^^^ ^^^^^^^ ^^^ It even shows the syntax and where things changed. Now, it's not an AI; this may not be the signature you wanted, exactly, but the point is, you don't need to memorize the elision rules. You just don't write any lifetimes until the compiler makes you deal with it, and then you deal with your problem, and move on with life.
- johnisgood 6y agoYeah, I do find the compiler errors useful, I just wish I did not have to see them to understand the code. I am not really trying to "trashtalk" Rust or anything, I just "keep on" expressing my opinion on my dislike of heavy use of symbols. That is all. :) I have written plenty of Perl code, too, and I was even proud of myself! Problem is that after a few weeks I could not tell what particular bits of code did. :D "There comes a time when words are more appropriate than symbols."
- steveklabnik 6y agoSo, every reference has a lifetime. But writing them out all the time isn't great for humans. So we looked at existing code, and found that the vast majority of lifetimes followed a few simple patterns. So we implemented these patterns as "elision rules," that is, patterns where you can not write, that is, elide, the lifetime. Here are the rules: 1. Every reference as a parameter gets its own lifetime parameter. There is only one in this case. 2. If there is exactly one, then every possible lifetime in the return type is assigned to it. 3. If there are multiple parameters, but it's a method, not a free function, then the lifetime assigned to self is assigned to all outputs. This is written in a bit of an abstract way, but, #2 there describes this function: we have one input, so we assign the same lifetime to all outputs, and there's only one. This means that it's not 'static, and in fact, it would fail to compile if it assumed that. The original RFC, if you're curious https://rust-lang.github.io/rfcs/0141-lifetime-elision.html https://rust-lang.github.io/rfcs/0141-lifetime-elision.html (And, I actually think there's been some tweaks over time; if you have no inputs, the outputs can turn into 'static... the core of it is these three things though. Oh yeah, that is in the reference, which is the up to date spec, https://doc.rust-lang.org/reference/lifetime-elision.html; https://doc.rust-lang.org/reference/lifetime-elision.html; I linked the RFC for historical reference because it's interesting, and has some neat stats.)
- oconnor663 6y ago(For some reason the semicolon at the end of your reference link seems to mess it up. Here's the working link: https://doc.rust-lang.org/reference/lifetime-elision.html https://doc.rust-lang.org/reference/lifetime-elision.html )
- 205guy 6y agoMaybe because a semicolon is valid in URLs. Although the top answer on stack overflow also says: "However, if you're plucking them from relatively unstructured prose, it's probably safe to assume a semicolon at the end of a URL is meant as sentence punctuation." Because you can never know how a given website will handle the input, I always leave a space after URL and before punctuation in a text box, like so: http://example.com http://example.com ;
- pornel 6y agoElision never assumes 'static, since that's an unusual case that has very few real-world uses. This leaves the only remaining possibility that references in the return type must have the same lifetime as references in function's arguments. Without a GC, or leaking memory, you can't make a Rust reference/lifetime out of thin air, so there's always such relationship. When a function takes multiple arguments by reference, the compiler will ask to disambiguate which one is used for the return type. Borrow checker intentionally checks borrowing against function prototypes, not function bodies.
- garmaine 6y agoI believe ‘static is assumed if there are no inputs.
- Manishearth 6y agoNo, not quite, the signature will be `fn foo<'a>() -> &'a u8` (there is no way to elide this) However, Rust lifetimes are stretchy-squeezy, and a free lifetime is basically the same as 'static when it comes to covariant lifetimes, since both are "I can be whatever you want me to be". However if the lifetime is invariant (e.g. in https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=33cfb6dfde60bb44e36a71b461e66724 https://play.rust-lang.org/?version=stable&mode=debug&editio...), it is actually not 'static and will map to whatever lifetime is requested by the context.
- brundolf 6y agoWibbly-wobbly timey-wimey? :)
- josalhor 6y agoThat's not the case. Proof: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0fed983276f36448315d36ec48d0f823 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- IshKebab 6y ago
- estebank 6y ago// this function foo fn foo(s: &str) -> &str { &s[0..1] } // is equivalent to this function fn foo_desugared<'a>(s: &'a str) -> &'a str { &s[0..1] } // this works too because the lifetime of "" is 'static because // it is a str that lives in the .data segment, which will live for "any 'a" fn bar(s: &str) -> &str { &"" } fn main() { // as shown here bar(&String::new()); } // but this won't work, because `x` gets `drop`ed at the end of `baz` fn baz(x: &str) -> &str { let x = String::new(); &x } https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5a41f59cc8be91607fc150b0389d1aa6 https://play.rust-lang.org/?version=stable&mode=debug&editio... Keep in mind that lifetimes have the opposite behavior of generic types. A T: Trait type parameter means "something that implements at least Trait, but might implement other things that you won't have access to", while an &'a returned borrow means "something that lives at least up to 'a, but might live less".
- Arnavion 6y ago>Keep in mind that lifetimes have the opposite behavior of generic types. A T: Trait type parameter means "something that implements at least Trait, but might implement other things that you won't have access to", while an &'a returned borrow means "something that lives at least up to 'a, but might live less". I don't know what you're trying to say here, but taken at face value it is wrong, and your own `fn bar` example demonstrates it being wrong. "something that lives at least up to 'a but might live less" is a contradiction. Perhaps you meant "... but might live longer" ? If so, it would be correct, and also would not be "the opposite behavior of generic types."
- estebank 6y agoGiven fn foo(x: &str) -> &str { x } calling `let z = foo(y);` is valid. The lifetime of `y` conforms to the lifetime of `x` in the `foo` signature, while the lifetime of `z` can be as long as the one for `x` (up to x's lifetime), but it is shorter (unless you try to do `drop(y)`, which would be a borrow checker error).
- 6y ago