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
(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; I linked the RFC for historical reference because it's interesting, and has some neat stats.)
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 ;
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.
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&editio...), it is actually not 'static and will map to whatever lifetime is requested by the context.
Proof: https://play.rust-lang.org/?version=stable&mode=debug&editio...
I'm pretty sure boxed trait objects (maybe all boxed objects?) are assumed to be 'static. That is, Box<dyn MyTrait> is shorthand for Box<dyn MyTrait<'static>). If you want non-static lifetimes you have to do something like Box<dyn MyTrait<'_> + '_>
(I only just learnt this so take it with a pinch of salt.)
TL;DR: Trait<'a> is actually understood as Trait<'a> + 'a.
https://play.rust-lang.org/?version=stable&mode=debug&editio...
[1]: https://github.com/rust-lang/rfcs/blob/master/text/0599-defa...
Also I'm going to point people here next time someone tries to claim that Rust is simple.
help: to declare that the trait object captures data from argument `self`, you can add an explicit `'_` lifetime bound
|
14 | fn things(&self) -> Box<dyn Things<'_> + '_> {
| ^^^^
Edit: having said that, the docs do explain these rules[1], reachable from the trait object documentation[2], while it seems that The Book doesn't.I'm also noticing that my confusion was likely due to `&'r Ref<'q, Trait>` being interpreted as `&'r Ref<'q, Trait+'q>`, which is behavior that was introduced in a follow up RFC[3].
[1]: https://doc.rust-lang.org/reference/lifetime-elision.html#de...
[2]: https://doc.rust-lang.org/reference/types/trait-object.html?...
[3]: https://github.com/rust-lang/rfcs/blob/master/text/1156-adju...
- 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
(Edited to improve formatting)
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.
// 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&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".
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."
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).>while an &'a returned borrow means "something that lives at most up to 'a, but might live less".
fn foo(bar: &x) -> &y
is inferred to be: fn foo<'a>(bar: &'a x) -> &'a yThis 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.
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.
"There comes a time when words are more appropriate than symbols."
I find Ada very hard to read. ... come to think of it, doesn’t it also have unbalanced apostrophes? It’s been a while.
> unbalanced apostrophes?
What are you referring to exactly? Can you give me an example? Do you mean something like Integer'Last, which is the largest value of type Integer?
> The concept of attributes is pretty unique to Ada. Attributes allow you to get - and sometimes set - information about objects or other language entities such as types. A good example is the Size attribute. It describes the size of an object or a type in bits:
type Byte is range -128 .. 127; -- The range fits into 8 bits but the compiler is still free to choose.
for Byte'Size use 8; -- Forces the compiler to use 8 bits.
I find it particularly neat.Of course one of the best things I love about Ada is pre- and post-conditions or contract-based programming (that are actually checked, unless in languages like Kotlin that just bolted on some contracts that are just there for the reader, i.e. pretty useless, to be honest). Ranged types are awesome, too, as you tell the compiler the range you want and the compiler chooses an appropriate sized integer to suit the range, which is the important bit. For example, instead of asking for "16-bit signed integer", you say: "I want a type which can store values between -1 and 1000", and maybe even a hint for the compiler where you can ask the compiler to pick you a faster type or a smaller type, so you say:
type Index is range -1 .. 1000;
Types like these are much more readable, and you can use attributes on them such as 'Min, 'Max, 'Range, and so forth. It is such a good feature, and you can remove the range checks if you want, and you can even have them checked at compile-time. These kind of features are what I would like to see from a C replacement.See more at https://learn.adacore.com/courses/intro-to-ada/chapters/stro....
---
https://www.adaic.org/learn/materials/intro/part1/
https://www.adaic.org/resources/add_content/docs/craft/html/...
https://www.adaic.org/advantages/features-benefits/
https://www.radford.edu/nokie/classes/320/designGoals.html
https://hackaday.com/2019/09/10/why-ada-is-the-language-you-...
Those links might give you some idea about the language and its goals, if you really are interested. Of course there are much more, up-to-date sources, but I think these got most of it right.
index = num(-1->1000);
or something. :DI dunno how attributes would work, but man, they really are handy. It is very, very difficult to shoot yourself in the foot.
I see what you did there (but maybe you don't see it yourself :-)
PS: at least MY parentheses are balanced.
That seems unlikely, or anyone - even if they hadn't learned to program ever - would be able to read Ada. More likely that a) you've already learned enough to cover Ada by learning other programming languages or b) you assume you know what the Ada code does but are incorrect.
Let's charitably go with a) then what you're implicitly supporting is that "all languages must look and behave the same, and there's no possible benefit in a language being different enough that it requires me to learn something new".
And therefore that the lowest-common-denominator intersection of all popular languages combined is also the pinnacle of language design.
Something which seems unlikely.
Perhaps you could compare some Ada code (something from AdaCore (GNAT, GNATColl)) with the Rust equivalent.
If you don't know Ada then you don't /know/ that you understand it, you're assuming you do - and if you do that's because it looks like languages you already know. And if that's what you're saying is good about it, then what you're saying is "things I know are good". You're seeing "Ada looks familiar" and saying "Ada has good design" as if those are the same thing. You only need to look at some beginner programming discussions to see that block-structured, scoped, procedural, looped, branched, etc. features are not things people are born understanding - that you know them already and Ada has them is convenient for you.
> "What the code does is not hidden behind symbols, for example."
I looked at the Wikipedia page, it has equals, colon-equals, slash-equals, greater-than, less-than, apostrophe, parentheses, ampersand, double-quotes, equals-greater-than which seems to be an arrow not a comparison, double-dots, and of course "when" "is" "protected" etc. they're as much symbols as they are words.
> "Ada and Rust, Ada is way much easier to understand"
I'll quote Erik Naggum from an old comp.lang.lisp(?) comment: "When I work at this system up to 12 hours a day, I'm profoundly uninterested in what user interface a novice user would prefer.".