Rewrite lifetimes

pull/67/head
Dhghomon 4 years ago committed by GitHub
parent c18cd96bc7
commit 721854f192
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6960,17 +6960,17 @@ There is more than one type of `&str`. We have:
- Borrowed str: This is the regular `&str` form without a `static` lifetime. If you create a `String` and get a reference to it, Rust will convert it to a `&str` when you need it. For example:
```rust
fn prints_str(my_str: &str) { // it can use &String like a &str
println!("{}", my_str);
}
fn main() {
let my_string = String::from("I am a string");
prints_str(&my_string); // we give prints_str a &String
}
fn prints_str(my_str: &str) { // it can use &String like a &str
println!("{}", my_str);
}
```
So what is a lifetime?
So what is a lifetime? We will learn that now.
## Lifetimes
@ -6990,15 +6990,15 @@ The problem is that `my_string` only lives inside `returns_reference`. We try to
This code also doesn't work:
```rust
fn main() {
let my_str = returns_str();
println!("{}", my_str);
}
fn returns_str() -> &str {
let my_string = String::from("I am a string");
"I am a str" // ⚠️
}
fn main() {
let my_str = returns_str();
println!("{}", my_str);
}
```
But it almost works. The compiler says:
@ -7022,22 +7022,24 @@ help: consider using the `'static` lifetime
Now it works:
```rust
fn main() {
let my_str = returns_str();
println!("{}", my_str);
}
fn returns_str() -> &'static str {
let my_string = String::from("I am a string");
"I am a str"
}
fn main() {
let my_str = returns_str();
println!("{}", my_str);
}
```
So now `fn returns_str() -> &'static str` tells Rust: "don't worry, we will only return a string literal". String literals live for the whole program, so Rust is happy.
That's because we returned a `&str` with a lifetime of `static`. Meanwhile, `my_string` can only be returned as a `String`: we can't return a reference to it because it is going to die in the next line.
So now `fn returns_str() -> &'static str` tells Rust: "don't worry, we will only return a string literal". String literals live for the whole program, so Rust is happy. You'll notice that this is similar to generics. When we tell the compiler something like `<T: Display>`, we promise that we will only use inputs with `Display`. Lifetimes are similar: we are not changing any variable lifetimes. We are just telling the compiler what the lifetimes of the inputs will be.
But `'static` is not the only lifetime. Actually, every variable has a lifetime, but usually we don't have to write it. We only have to write the lifetime when the compiler doesn't know.
But `'static` is not the only lifetime. Actually, every variable has a lifetime, but usually we don't have to write it. The compiler is pretty smart and can usually figure out for itself. We only have to write the lifetime when the compiler doesn't know.
Here is an example of another lifetime. Imagine we want to create a `City` struct and give it a `&str` for the name. Later we will have many more `&str`s because we need faster performance than with `String`. So we write it like this, but it won't work:
Here is an example of another lifetime. Imagine we want to create a `City` struct and give it a `&str` for the name. We might want to do that because it gives faster performance than with `String`. So we write it like this, but it won't work yet:
```rust
#[derive(Debug)]
@ -7128,6 +7130,8 @@ error[E0597]: `city_names` does not live long enough
| - `city_names` dropped here while still borrowed
```
This is important to understand, because the reference we gave it actually lives long enough. But we promised that we would only give it a `&'static str`, and that is the problem.
So now we will try what the compiler suggested before. It said to try writing `struct City<'a>` and `name: &'a str`. This means that it will only take a reference for `name` if it lives as long as `City`.
```rust
@ -7149,7 +7153,7 @@ fn main() {
}
```
Also remember that you can write anything instead of `'a` if you want:
Also remember that you can write anything instead of `'a` if you want. This is also similar to generics where we write `T` and `U` but can actually write anything.
```rust
#[derive(Debug)]
@ -7161,9 +7165,9 @@ struct City<'city> { // The lifetime is now called 'city
fn main() { }
```
So usually you will write `'a, 'b, 'c` etc. because it is quick and the usual way to write. But you can always change it if you want.
So usually you will write `'a, 'b, 'c` etc. because it is quick and the usual way to write. But you can always change it if you want. One good tip is that changing the lifetime to a "human-readable" name can help you read code if it is very complicated.
Also remember this important fact: `'a` etc. don't change the actual lifetime of variables. They are like traits for generics. Remember when we wrote generics? For example:
Let's look at the comparison to traits for generics again. For example:
```rust
use std::fmt::Display;
@ -7193,6 +7197,192 @@ fn main() { }
It means "please only take an input for `name` if it lives at least as long as `City`".
It does not mean: "I will make the input for `name` live as long as `City`".
Now we can learn about `<'_>` that we saw before. This is called the "anonymous lifetime" and is an indicator that references are being used. Rust will suggest it to you when you are implementing structs, for example. Here is one struct that almost works, but not yet:
```rust
// ⚠️
struct Adventurer<'a> {
name: &'a str,
hit_points: u32,
}
impl Adventurer {
fn take_damage(&mut self) {
self.hit_points -= 20;
println!("{} has {} hit points left!", self.name, self.hit_points);
}
}
fn main() {
}
```
So we did what we needed to do for the `struct`: first we said that `name` comes from a `&str`. That means we need a lifetime, so we gave it `<'a>`. Then we had to do the same for the `struct` to show that they are at least as long as this lifetime. But then Rust tells us to do this:
```text
error[E0726]: implicit elided lifetime not allowed here
--> src\main.rs:6:6
|
6 | impl Adventurer {
| ^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`
```
It wants us to add that anonymous lifetime to show that there is a reference being used. So if we write that, it will be happy:
```rust
struct Adventurer<'a> {
name: &'a str,
hit_points: u32,
}
impl Adventurer<'_> {
fn take_damage(&mut self) {
self.hit_points -= 20;
println!("{} has {} hit points left!", self.name, self.hit_points);
}
}
fn main() {
}
```
This lifetime was made so that you don't always have to write things like `impl<'a> Adventurer<'a>`, because the struct already shows the lifetime.
Lifetimes can be difficult in Rust, but here are some tips to avoid getting too stressed about them:
- You can stay with owned types, use clones etc. if you want to avoid them for the time being
- Much of the time, when the compiler wants a lifetime you will just end up writing <'a> here and there and then it will work. It's just a way of saying "don't worry, I won't give you anything that doesn't live long enough"
- You can explore lifetimes just a bit at a time. Write some code with owned values, then make one a reference. The compiler will start to complain, but also give some suggestions. And if it gets too complicated, you can undo it and try again next time.
Let's do this with our code and see what the compiler says. First we'll go back and take the lifetimes out, and also implement `Display`. `Display` will just print the `Adventurer`'s name.
```rust
// ⚠️
struct Adventurer {
name: &str,
hit_points: u32,
}
impl Adventurer {
fn take_damage(&mut self) {
self.hit_points -= 20;
println!("{} has {} hit points left!", self.name, self.hit_points);
}
}
impl std::fmt::Display for Adventurer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} has {} hit points.", self.name, self.hit_points)
}
}
fn main() {
}
```
First complaint is this:
```text
error[E0106]: missing lifetime specifier
--> src\main.rs:2:11
|
2 | name: &str,
| ^ expected named lifetime parameter
|
help: consider introducing a named lifetime parameter
|
1 | struct Adventurer<'a> {
2 | name: &'a str,
|
````
It suggests what to do: `<'a>` after Adventurer, and `&'a str`. So we do that:
```rust
// ⚠️
struct Adventurer<'a> {
name: &'a str,
hit_points: u32,
}
impl Adventurer {
fn take_damage(&mut self) {
self.hit_points -= 20;
println!("{} has {} hit points left!", self.name, self.hit_points);
}
}
impl std::fmt::Display for Adventurer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} has {} hit points.", self.name, self.hit_points)
}
}
fn main() {
}
```
Now it's happy with those parts, but is wondering about the `impl` blocks. It wants us to mention that it's using references:
```text
error[E0726]: implicit elided lifetime not allowed here
--> src\main.rs:6:6
|
6 | impl Adventurer {
| ^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`
error[E0726]: implicit elided lifetime not allowed here
--> src\main.rs:12:28
|
12 | impl std::fmt::Display for Adventurer {
| ^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`
```
Okay, so we will write those in...and now it works! Now we can make an `Adventurer` and do some things with it.
```rust
struct Adventurer<'a> {
name: &'a str,
hit_points: u32,
}
impl Adventurer<'_> {
fn take_damage(&mut self) {
self.hit_points -= 20;
println!("{} has {} hit points left!", self.name, self.hit_points);
}
}
impl std::fmt::Display for Adventurer<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} has {} hit points.", self.name, self.hit_points)
}
}
fn main() {
let mut billy = Adventurer {
name: "Billy",
hit_points: 100_000,
};
println!("{}", billy);
billy.take_damage();
}
```
This prints:
```text
Billy has 100000 hit points.
Billy has 99980 hit points left!
```
So you can see that lifetimes are often just the compiler wanting to make sure. And it is usually smart enough to almost guess at what lifetimes you want, and just needs you to tell it so it can be certain.
## Interior mutability
### Cell

Loading…
Cancel
Save