08. Lifetimes

08. Lifetimes

08. Lifetimes

3 ноември 2017

Административни въпроси

Преговор

Lifetime анотации

Внимание: това ще е странно

Lifetimes

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}

Lifetimes

error: `x` does not live long enough
   |
6  |         r = &x;
   |              - borrow occurs here
7  |     }
   |     ^ `x` dropped here while still borrowed
...
10 | }
   | - borrowed value needs to live until here

Lifetimes

{
    let r;                // -------+-- 'a
                          //        |
    {                     //        |
        let x = 5;        // -+-----+-- 'b
        r = &x;           //  |     |
    }                     // -+     |
                          //        |
    println!("r: {}", r); //        |
                          //        |
}                         // -------+

Lifetimes

{
    let x = 5;            // -----+-- 'b
                          //      |
    let r = &x;           // --+--+-- 'a
                          //   |  |
    println!("r: {}", r); //   |  |
                          // --+  |
}                         // -----+

"Lifetime" vs "Scope"

fn foo() {
    let mut data = vec!['a', 'b', 'c']; // --+ 'scope
                                        //   |
                                        //   |
    capitalize(&mut data[..]);          //   |
//  ^~~~~~~~~~~~~~~~~~~~~~~~~ 'lifetime //   |
    data.push('d');                     //   |
    data.push('e');                     //   |
    data.push('f');                     //   |
} // <---------------------------------------+

fn capitalize(data: &mut [char]) {
    // do something
}

"Lifetime" vs "Scope"

fn foo() {
    let mut data = vec!['a', 'b', 'c']; // --+ 'scope
                                        //   |
    let slice = &mut data[..];          // --| 'lifetime
    capitalize(slice);                  //   |
                                        //   |
    data.push('d'); // комп. грешка     //   |
    data.push('e'); // комп. грешка     //   |
    data.push('f'); // комп. грешка     //   |
} // <---------------------------------------+

fn capitalize(data: &mut [char]) {
    // do something
}

Функции, които връщат references

fn longest(x: &str, y: &str) -> String {
    if x.len() > y.len() {
        String::from(x)
    } else {
        String::from(y)
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(&string1, string2);
    println!("По-дългия низ е {}", result);
}

Функции, които връщат references

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(&string1, string2);
    println!("По-дългия низ е {}", result);
}

Функции, които връщат references

error[E0106]: missing lifetime specifier
   |
1  | fn longest(x: &str, y: &str) -> &str {
   |                                 ^ expected 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`

Функции, които връщат references

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(&string1, string2);
    println!("По-дългия низ е {}", result);
}

Функции, които връщат references

fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(&string1, &string2);
        println!("The longest string is {}", result);
    }
}

Функции, които връщат references

fn main() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(&string1, &string2);
    }
    println!("The longest string is {}", result);
}
error: `string2` does not live long enough
   |
6  |         result = longest(&string1, &string2);
   |                                     ------- borrow occurs here
7  |     }
   |     ^ `string2` dropped here while still borrowed
8  |     println!("The longest string is {}", result);
9  | }
   | - borrowed value needs to live until here

Lifetime elision

Но ние вече сме виждали функции, които връщат референция

fn first_word(s: &str) -> &str {
    // код
}

Защо тогава работеше, а сега не?

Lifetime elision

Горната дефиниция е еквивалентна на:

fn first_word<'a> (s: &'a str) -> &'a str {
    // код
}

Lifetime elision

Кога трябва да пишем lifetimes?

Тяло на функция

Дефиниция на функция

Структура

Lifetime elision

Как работи

За всеки пропуснат lifetime в аргументите се добавя lifetime параметър

fn print(s: &str);                                  // elided
fn print<'a>(s: &'a str);                           // expanded

fn foo(x: (&u32, &u32), y: usize);                  // elided
fn foo<'a, 'b>(x: (&'a u32, &'b u32), y: usize);    // expanded

Lifetime elision

Как работи

Ако за аргументите има само един lifetime параметър (експлицитен или пропуснат), този lifetime се налага на всички пропуснати lifetimes в резултата

fn substr(s: &str, until: usize) -> &str;                         // elided
fn substr<'a>(s: &'a str, until: usize) -> &'a str;               // expanded

fn split_at(s: &str, pos: usize) -> (&str, &str);                 // elided
fn split_at<'a>(s: &'a str, pos: usize) -> (&'a str, &'a str);    // expanded

Lifetime elision

Как работи

Ако първият аргумент е &self или &mut self, неговият lifetime се налага на всички пропуснати lifetimes в резултата

fn get_mut(&mut self) -> &mut T;                                // elided
fn get_mut<'a>(&'a mut self) -> &'a mut T;                      // expanded

fn args(&mut self, args: &[T]) -> &mut Self;                    // elided
fn args<'a, 'b>(&'a mut self, args: &'b [T]) -> &'a mut Self;   // expanded

Lifetime elision

Как работи

Във всички останали случаи е грешка да пропуснем lifetime-а.

fn get_str() -> &str;                     // комп. грешка
fn longest(x: &str, y: &str) -> &str;     // комп. грешка

Статичен живот

Статичен живот

Специалният lifetime 'static

let s: &'static str = "I have a static lifetime.";

Kонстанти

const

Константи

const

const EULER_NUMBER: f32 = 2.71828;
const HI: &str = "hi";                 // &'static str
const HI: String = String::from("hi"); // ??

fn main() {
    const N: i32 = 7;

    println!("{}", EULER_NUMBER);
}

Константи

Асоциирани константи

struct Foo;

impl Foo {
    const N: i32 = 7;
}

fn main() {
    println!("{}", Foo::N);
}

Статични променливи

static

Статични променливи

static

static NAME: &str = "Steve";
static NAME: &'static str = "Steve";

Статични променливи

static mut

Можете да направите статичната променлива да е mutable, но има недостатъци

static mut N: i32 = 7;

Статични променливи

static mut

Тъй като може да имате различни нишки които четат или пишат от променливата по едно и също време, за да четете или пишете към нея, трябва да използвате unsafe блок.

fn f() {
    static mut N: i32 = 7;

    unsafe {
        N += 1;
        println!("{}", N);
    }
}

for i in 0..3 {
    f();          // ??
}

Референции в структури

struct TextInfo {
    text: String,
}

impl TextInfo {
    fn new(text: &str) -> TextInfo {
        TextInfo {
            text: String::from(text)
        }
    }
}

Можем ли да избегнем копирането на низа?

Референции в структури

Можем да пробваме със &'static str

struct TextInfo {
    text: &'static str,
}

impl TextInfo {
    fn new(text: &str) -> TextInfo {
        TextInfo { text }
    }
}

TextInfo::new("баба");          // ?

let s = String::from("дядо");
TextInfo::new(&s);              // ?

Референции в структури

Всъщност не искаме 'static, искаме произволен lifetime

struct TextInfo {
    text: &'a str,
}
error[E0261]: use of undeclared lifetime name `'a`
--> src/main.rs:3:16
  |
3 |         text: &'a str,
  |                ^^ undeclared lifetime 

Референции в структури

Трябва да си дефинираме lifetime-а като generic parameter

struct TextInfo<'a> {
    text: &'a str,
}

Референции в структури

Трябва да си дефинираме lifetime-а като generic parameter

struct TextInfo<'a> {
    text: &'a str,
}

Съответно трябва да го добавим и на impl блока

impl<'a> TextInfo<'a> {
    fn new(text: &str) -> TextInfo {
        TextInfo { text }
    }
}

Референции в структури

Сега вече всичко работи

fn main() {
    let t1 = TextInfo::new("баба"); // TextInfo<'static>

    let s = String::from("дядо");   // ---+- 'a
    let t2 = TextInfo::new(&s);     //    |-  TextInfo<'a>
                                    //    |
}                                   // ---+

Референции в структури

Животът на структурата е ограничен до това колко живее обектът, от който сме взели референция

let info;

{
    let s = String::from("мой таен низ");   // ---+- 'a
    info = TextInfo::new(&s);               //    |- TextInfo<'a>
}                                           // ---+

println!("{:?}", info);

Lifetime elision в impl блок

Как се попълват пропуснатите lifetimes за функцията new?

impl<'a> TextInfo<'a> {
    fn new(text: &str) -> TextInfo {
    }
}

Lifetime elision в impl блок

Expanded

impl<'a> TextInfo<'a> {
    fn new<'b>(text: &'b str) -> TextInfo<'b> {
    }
}

Повече референции

Какво става ако имаме повече от една референция?

struct TwoRefs<'a, 'b> {
    text1: &'a str,
    text2: &'b str,
}

let r;
{
    let second = String::from("second");
    r = TwoRefs { text1: "first", text2: &second };
    println!("{}", r.text2);    // ?
}
println!("{}", r.text1);        // ?

Повече референции

Структурата живее колкото най-малкия lifetime

Затова обикновено е безсмислено да има повече от един lifetime параметър за структура

struct TwoRefs<'a> {
    text1: &'a str,
    text2: &'a str,
}

let second = String::from("second");
let r = TwoRefs {
    text1: "first",         // автоматично конвертиране &'static str -> &'a str
    text2: &second,         // за да съвпадне с lifetime-а на second
};

Компилаторът автоматично конвертира от по-голям към по-малък lifetime

Референции и шаблони в структури

Не на последно място, ако ни се наложи да декларираме lifetimes и generics заедно, редът на декларация е lifetimes преди generics

struct TextInfo<'a, T> {
    text: &'a str,
    count: T
}

// Компилационна грешка
struct TextInfo<T, 'a> {
    text: &'a str,
    count: T
}

Въпроси