Lifetime
Lifetime is a new concept in Rust and does not exist in C/C++. The purpose of lifetime is to prevent a variable from being accessed after it has been destroyed.
Lifetime declaration in function
Let consider this C++ program :
#include <iostream>
#include <string>
using namespace std;
struct Person
{
string name;
int age;
};
Person createPerson(const char* name, int age)
{
return (Person){name, age};
}
void printInfo(const Person* person)
{
cout << person->name << " , " << person->age << " years old" << endl;
}
Person* findYounger(Person* p1, Person* p2)
{
return p1->age < p2->age? p1 : p2;
}
int main()
{
Person *younger;
Person p1 = createPerson("Person 1", 20);
{
Person p2 = createPerson("Person 2", 10);
younger = findYounger(&p1, &p2);
}
printInfo(younger);
return 0;
}
For some C++ compilers, the above program will print unexpected result. The reason is because the pointer younger points to person p2 which has become invalid when the pointer younger is accessed at the last line.
To prevent such scenarios, Rust introduces the concept of lifetime. Let consider the equivalent program in Rust :
struct Person
{
name : String,
age : i32
}
fn createPerson(name : &str, age : i32) -> Person
{
Person {name : name.to_owned(), age : age}
}
fn printInfo(person: &Person)
{
println!("{} , {} years old", person.name, person.age);
}
fn findYounger<'a>(p1 : &'a Person, p2: &'a Person) -> &'a Person
{
if p1.age < p2.age { p1 } else { p2 }
}
fn main()
{
let p1 = createPerson("Person 1", 20);
let younger;
{
let p2 = createPerson("Person 2", 10);
younger = findYounger(&p1, &p2);
}
// The following line cause a compilation error
printInfo(younger);
}
The single quote ' is for lifetime annotation. As you get familiar with Rust, you will get used to this annotation. The function findYounger defines lifetime 'a and requires all input references to have the same lifetime as this, so the returned value also has lifetime 'a. In the main function, when function findYounger is called, it requires the three variables : the two input variables p1,p2 and the output variable younger, to have the same lifetime, i.e. they belong to a same scope. However, in this case, the scope of variable p2 is smaller than that of variable p1 and variable younger, and that causes a compilation error.
There can be cases where there are several lifetime annotations in a function, in those cases the compiler needs to make sure all variables with same lifetime annotation in function signature, have the same lifetime when the function is called.
Let consider the following example :
struct Person
{
name : String,
age : i32
}
fn createPerson(name : &str, age : i32) -> Person
{
Person {name : name.to_owned(), age : age}
}
fn printInfo(person: &Person)
{
println!("{} , {} years old", person.name, person.age);
}
fn getFirst<'a,'b>(p1 : &'a Person, p2: &'b Person) -> &'a Person
{
p1
}
fn main()
{
let p1 = createPerson("Person 1", 20);
let first;
{
let p2 = createPerson("Person 2", 10);
first = getFirst(&p1, &p2);
}
printInfo(first);
}
This program does not have errors, because the function getFirst only requires the first parameter and the returned value to have the same lifetime. In the main function, when function getFirst is called, the first parameter is variable p1 and the returned value is stored in variable first, both of them belong to the same scope, so the borrow checker of the compiler has no complain.
Lifetime declaration in struct
Let consider the following C++ program :
#include <iostream>
#include <string>
using namespace std;
struct Address
{
string street;
string city;
};
Address createAddress(const char* street, const char* city)
{
return (Address) {street, city};
}
struct Person
{
string name;
int age;
Address* address;
};
Person createPerson(const char* name, int age, Address* address)
{
return (Person){name, age, address};
}
void setNewAddress(Person* person, Address* newAddress)
{
person->address = newAddress;
}
void printInfo(const Person* person)
{
cout << person->name << " , " << person->age << " years old" <<
" @ " << person->address->street << " , " << person->address->city << endl;
}
int main()
{
Address address = createAddress("Street 1", "City 1");
Person person = createPerson("Person 1", 20, &address);
printInfo(&person);
{
Address newAddress = createAddress("Street 2", "City 2");
setNewAddress(&person, &newAddress);
}
printInfo(&person);
return 0;
}
This program may print out unexpected result because the variable newAddress referred by variable person has become invalid when variable person is accessed at the last line.
To prevent this, Rust requires all pointers in struct to have specified lifetime annotations. Here is the program in Rust:
struct Address
{
street: String,
city : String
}
fn createAddress(street : &str, city : &str) -> Address
{
Address {street : street.to_owned(), city : city.to_owned()}
}
struct Person<'a>
{
name : String,
age : i32,
address : &'a Address
}
fn createPerson<'a>(name : &str, age : i32, address: &'a Address) -> Person<'a>
{
Person {name : name.to_owned(), age : age, address : address}
}
fn printInfo(person: &Person)
{
println!("{} , {} years old @ {}, {}", person.name, person.age, person.address.street, person.address.city);
}
fn setNewAddress<'a>(person: &mut Person<'a>, newAddress : &'a Address)
{
person.address = newAddress;
}
fn main()
{
let address = createAddress("Street 1", "City 1");
let mut person = createPerson("Person 1", 20, &address);
printInfo(&person);
{
let newAddress = createAddress("Street 2", "City 2");
setNewAddress(&mut person, &newAddress);
}
printInfo(&person);
}
This program will not compile because the variable newAddress and variable person do not have the same lifetime. In order to compile the program, we need to remove the curly brackets surrounding the declaration of variable newAddress to make it have the same lifetime as that of the variable person