LINUX.ORG.RU

Время жизни в Rust

 , , ,


0

2

И снова здравствуйте! Пытаюсь разобраться с системой владения ресурсами в Rust. Пока что не понятно для чего нужно время жизни и как использовать его вообще. Например, есть у меня функция

fn foo<'a>(x: &'a i32) {

}

Как это все понимать? Это означает, что время жизни функции должно быть не больше, чем время жизни ссылки x? Получается, что время жизни это некая гарантия(обещание), что мы не будем использовать ссылки после того, как их не станет?


Ну, создайте шесть тем. Что на двух то останавливаться.

Благодаря времени жизни компилятор убедится, что ссылка переживёт вашу функцию. Вы одалживаете функции некий объект и владелец этого объекта не должен быть умертвлён раньше, чем функция перестанет работать.

Weres ★★★
()

Получается, что время жизни это некая гарантия(обещание), что мы не будем использовать ссылки после того, как их не станет?

Компилятор не даст тебе использовать объект после того, как его время жизни закончится. Лайфтаймы нужны, чтобы указывать соотношение времен жизни разных объектов или накладывать на них явные ограничения.

tailgunner ★★★★★
()

Собственно, для функции foo указывать время жизни не нужно. foo1 ниже это абсолютно то же самое, что и foo2. Компилятор в этом случае может сам проставить время жизни.

Время жизни функции не имеет никакого отношения к 'a, 'a относится только к ссылке x (точнее к значению, на которое ссылается x). Независимо от указания или неуказания 'a, гарантируется, что ссылка будет валидной в течение выполнения функции.

fn foo1<'a>(x: &'a i32) {}
fn foo2(x: &i32) {}
fn foo3<'a,'b>(x: &'a i32, y: &'b i32) -> SomeStruct<'b> {}

Указание времён жизни требуется в более сложных ситуациях. Например, foo3 возвращает структуру, которая как-то использует ссылку y, в этом случае нужно указать, что время жизни структуры зависит от y.

Если структура может использовать как x так и y, то нужно написать.

fn foo4<'a>(x: &'a i32, y: &'a i32) -> SomeStruct<'a> {}

В этом случаем компилятор попытается подобрать время жизни валидное для обеих ссылок.

Как-то так.

anonymous
()
Ответ на: комментарий от anonymous

То есть время жизни относится исключительно к ссылкам? Вернее к значениям, на которые существует ссылка? В последнем примере компилятор подберет наибольшее время жизни?

paret
() автор топика
Ответ на: комментарий от paret

То есть время жизни относится исключительно к ссылкам? Вернее к значениям, на которые существует ссылка?

Да. Но я там не совсем правильно написал. Время жизни значения это одно. Время жизни ссылки - другое. Гарантируется, что время жизни значения больше времени жизни ссылки на него.

Явно можно задать только одно время жизни: 'static (объект/ссылка валидна в течении всего времени выполнения программы), остальные значения времён жизни подставляются компилятором.

В последнем примере компилятор подберет наибольшее время жизни?

Да. Наибольшее время жизни, в течении которого валидны обе ссылки. То есть, фактически, наименьшее из времен жизни x и y.

anonymous
()
Ответ на: комментарий от paret

Я больше скажу, ты не можешь не думать о времени жизни в плюсах, например. Но раст предлагает: давайте думать об этом открыто. В твоем примере это может быть не очень имеет смысл. Именно потому он и попадает под действие вывода лайфтаймов.

То есть время жизни относится исключительно к ссылкам? Вернее к значениям, на которые существует ссылка?

Да. И к слайсам.

В последнем примере компилятор убедится что на момент возврата значения, оба аргумента ещё существуют.

Esteban_Garcia
()
Ответ на: комментарий от anonymous

То есть, фактически, наименьшее из времен жизни x и y.

Ты забыл добавить, что на наименьшее из времен жизни так же влияет время жизни результата.

fn foo<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
    if *x != 0 { x } else { y }
}

fn main() {
    let x = 2;
    let mut result;
    {
        let y = 3;
        let t = foo(&x, &y); // ok
        result = foo(&x, &y); // error: `y` does not live long enough
    }
}
anonymous
()
Ответ на: комментарий от paret

Более конкретно.

// Псевдокод с явным указанием времени жизни
'a: {
  let a = 1u32;
'b: {
  let b = &a;
// let s = // Ошибка. Время жизни структуры, возвращаемой foo4 меньше 'b
'c: {
  let c = &a;
  foo4<'c>(b, c)
}
//  dosomething(s); // Ошибка. Ссылка c здесь невалидна
}}
anonymous
()
Ответ на: комментарий от anonymous

Или y уничтожается при выходе из области видимости foo()?

paret
() автор топика
Ответ на: комментарий от Esteban_Garcia

Вот что я получаю при компиляции этого примера.

Executing: cargo build Compiling first v0.1.0 (file:///D:/projects/rust/first) src\main.rs:11:27: 11:28 error: `y` does not live long enough src\main.rs:11 result = foo(&x, &y); // error: `y` does not live long enough ^ src\main.rs:7:20: 13:2 note: reference must be valid for the block suffix following statement 1 at 7:19... src\main.rs: 7 let mut result; src\main.rs: 8 { src\main.rs: 9 let y = 3; src\main.rs:10 let t = foo(&x, &y); // ok src\main.rs:11 result = foo(&x, &y); // error: `y` does not live long enough src\main.rs:12 } ... src\main.rs:9:19: 12:6 note: ...but borrowed value is only valid for the block suffix following statement 0 at 9:18 src\main.rs: 9 let y = 3; src\main.rs:10 let t = foo(&x, &y); // ok src\main.rs:11 result = foo(&x, &y); // error: `y` does not live long enough src\main.rs:12 } error: aborting due to previous error Could not compile `first`.

To learn more, run the command again with --verbose. Cargo: build (debug)src\main.rs14:1 LFUTF-8Rust+14

Просто если я y объявляю вне блока, то все компилируется, так же если объявляю result в блоке, то все нормально компилируется.

paret
() автор топика
Ответ на: комментарий от paret

Прошу прощения. Все поехало к чертям. Не знаю как оформить вывод компилятора.

paret
() автор топика
Ответ на: комментарий от paret

Так в этом и суть. Ты не можешь присвоить ссылку на что-то что перестанет существовать тому, что существовать продолжит. В примере лайфтайм результата — пересечение лайфтаймов x и y, как только у перестает существовать, значение возвращенное функцией тоже. А result остается.

Esteban_Garcia
()
Ответ на: комментарий от Esteban_Garcia

Ну воооот! Теперь то точно все понятно. Спасибо:)

paret
() автор топика
Ответ на: комментарий от Esteban_Garcia

Еще один вопрос. По сути лайфтаймы определяются областью видимости? И ошибка в примере из-за того, что «result» и «y» в разных областях видимости так? То есть «y» умрет в блоке, в то время как result будет пытаться получить значение этого мертвого «y». Я правильно понял?

paret
() автор топика
Ответ на: комментарий от paret

Да, хорошо про лайфтаймы думать как про области видимости. Если ты обозначил лайфтайм как <'a> то значит все что 'a должно быть в одной области видимости.

Esteban_Garcia
()
Ответ на: комментарий от paret

Вот ещё как хорошо это понимать. Когда ты возвращаешь ссылку или слайс, то она не может взяться из неоткуда. Она дожна быть либо явной ссылаться либо на какой-то из аргументов(возможно на несколько), либо на окружение. Потому, указывая лайфтайм, ты указываешь, на что именно ссылается результат.

Esteban_Garcia
()
Ответ на: комментарий от DarkEld3r

Да. Впрочем, ссылка может быть и внутри структуры, например. Правильнее было бы сказать, ко всему что содержит ссылки(использует заимствование).

Esteban_Garcia
()
Ответ на: комментарий от Esteban_Garcia

Кроме ссылок еще может быть и PhantomData, для аннотирования прочих связей по времени жизни. Я эту штуку пару раз во врапперах для c-интерфейса использую.

anonymous
()
Ответ на: комментарий от DarkEld3r

Так ведь в PhantomData придётся «завернуть» как раз ссылку, разве нет?

Нет. PhantomData имеет нулевой размер. Включается в структуру, чтобы было где прописать время жизни.

anonymous
()
Ответ на: комментарий от anonymous

PhantomData имеет нулевой размер. Включается в структуру, чтобы было где прописать время жизни.

В курсе.

Нет.

Покажи пример. Разве там будет не что-то такое (пример из документации):

struct Slice<'a, T:'a> {
    ...
    phantom: PhantomData<&'a T>
}
Ведь написать просто PhantomData<'a> не получится. В итоге, лайфтайм всё равно будет применён к ссылке, пусть и «несуществующей».

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Покажи пример.

    pub fn from_slice<'a>(v: &'a [u8]) -> ShaderBytecode<'a> {
        ShaderBytecode {
            inner: D3D12_SHADER_BYTECODE {
                pShaderBytecode: v.as_ptr() as *const _,
                BytecodeLength: v.len() as SIZE_T,
            },
            ph: PhantomData::<&'a _>,
        }
    }

Вопрос терминологии. Но оборачиванием ссылки я бы это не назвал.

anonymous
()
Ответ на: комментарий от anonymous

Вопрос терминологии. Но оборачиванием ссылки я бы это не назвал.

Согласен, что это несущественно, но просто ради спора: насколько я вижу, ShaderBytecode выглядит вот так:

pub struct ShaderBytecode<'a> {
    inner: D3D12_SHADER_BYTECODE,
    ph: PhantomData<&'a u32>,
}
То есть, в итоге там именно ссылка на i32.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

Приветствую. Подскажи, можно ли реализовать Display для Vec? Что-нибудь вроде этого:

impl<T> fmt::Display for Vec<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for el in self {
            write!(f, "{}", el)
        }
    }
}

paret
() автор топика
Ответ на: комментарий от paret

Нет. Дело в том, что в Rust реализовать трейт можно только в том случае, если:

  • тип объявлен в том же модуле.
    mod foo {
      use Bar;
    
      struct Foo;
    
      impl Bar for Foor { /* impls */ }
    }
    
  • трейт объявлен в том же модуле.
    mod bar {
      use Foo;
    
      trait Bar { /* methods */ }
    
      impl Bar for Foo { /* impls */ }
    }
    

The first is that if the trait isn’t defined in your scope, it doesn’t apply.

...

There’s one more restriction on implementing traits: either the trait, or the type you’re writing the impl for, must be defined by you.

rust book

Как вариант, можно сделать обёртку.

use std::fmt;

struct Printer<'a, T: 'a>(&'a T);

impl<'a, T: fmt::Display> fmt::Display for Printer<'a, Vec<T>> {
	fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
		for e in self.0 {
			try!(writeln!(fmt, "{}", e));
		}
		Ok(())
	}
}

fn main() {
	let v = vec![1, 2, 3];
	println!("{}", Printer(&v));
}

anonymous
()
Ответ на: комментарий от Oxdeadbeef

ржавчик такой няшн^Wдобрый, аж расцветка поломалась. 😂

Просто на лоре, хреновая подсветка синтаксиса.

anonymous
()
Ответ на: комментарий от anonymous

не проще тогда написать функцию, которая в цикле будет выводить элементы вектора, вместо оберток этих. То есть в Rust нельзя расширять не свои структуры?

paret
() автор топика
Ответ на: комментарий от paret

не проще тогда написать функцию

Это уже как тебе больше нравится.

То есть в Rust нельзя расширять не свои структуры?

Почему нельзя? Можно: писать для них дополнительные методы или реализовывать свои трейты.

Нельзя «только» реализовывать чужие трейты для чужих структур.

Поначалу меня это тоже сильно напрягало, сейчас меньше, хотя с тем насколько это неудобное ограничение пока не определился. В любом случае, если разрешать кому угодно реализовывать какие угодно трейты, то надо было бы пересматривать (усложнять) механизм импорта трейтов. Как по мне, оно того стоило бы, но создатели языка решили иначе.

DarkEld3r ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.