Заимствованное значение не живет достаточно долго ошибка компилятора для структуры

Я новичок в языке и все еще борюсь с проверкой заимствований. Я видел, что некоторые библиотеки используют функции new (), также известные как конструкторы без параметров, и это работает. В основном это означает, что возвращаемые данные создаются внутри области новой функции, а не удаляются в конце области действия new.

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

Я что-то упускаю?

#[derive(Debug)]
struct B<'a> {
    b: &'a i32
}

#[derive(Debug)]
struct A<'a> {
    one: B<'a>
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        // let mut b = 10i32;
        A {
            one: B{b: &mut 10i32}
        }
    }
}

fn main() {
    let a = A::new();
    println!("A -> {:?}", a);
}

Ошибка компилятора.

main.rs:15:19: 15:24 error: borrowed value does not live long enough
main.rs:15          one: B{b: &mut 10i32}
                                   ^~~~~
main.rs:12:20: 17:3 note: reference must be valid for the lifetime 'a as defined on the block at 12:19...
main.rs:12  fn new() -> A<'a> {
main.rs:13      // let mut b = 10i32;
main.rs:14      A {
main.rs:15          one: B{b: &mut 10i32}
main.rs:16      }
main.rs:17  }
main.rs:12:20: 17:3 note: ...but borrowed value is only valid for the block at 12:19
main.rs:12  fn new() -> A<'a> {
main.rs:13      // let mut b = 10i32;
main.rs:14      A {
main.rs:15          one: B{b: &mut 10i32}
main.rs:16      }
main.rs:17  }
error: aborting due to previous error

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

let assets = find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets").unwrap();
let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf");
let theme = Theme::default();
let glyph_cache = GlyphCache::new(&font_path).unwrap();
let ui = &mut Ui::new(glyph_cache, theme);

Мой план состоял в том, чтобы инкапсулировать рисунок приложения в структуру. У него будет конструктор и несколько вспомогательных методов. Чтобы это сработало, мне нужно было бы иметь поле с экземпляром типа conrod::Ui<GlyphCache<'a>>, который является типом для переменной ui, указанной выше.

Я думаю, что добавление вещей в main (я имею в виду, что все распределения выполняются в main) может быть не лучшим способом сделать что-то.

let mut app_ui = app::AppUi::new(); // This would encapsulate all of the above configuration lines.

// use the ui here
for e in evets {
    app_ui.handle_input();
    app_ui.render();
}

Реализация AppUi. Он не завершен, но должен показать общую идею. Чтобы убедиться, что мы находимся на одной странице, для типа conrod::Ui<GlyphCache<'a>> требуется параметр времени жизни. И я хочу, чтобы время жизни было таким же, как у структуры. Единственный способ, которым я знаю, как это сделать, - заставить структуру получить параметр времени жизни и передать его типу пользовательского интерфейса.

pub struct AppUi<'a> {
  pub ui: conrod::Ui<GlyphCache<'a>>,
  pub count: u16
}

impl<'a> AppUi<'a> {
  pub fn new() -> AppUi<'a> {
    let assets = find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets").unwrap();

    let font_path = assets.join("FiraSans-Regular.ttf");
    let theme = Theme::default();
    let glyph_cache = GlyphCache::new(&font_path).unwrap();

    AppUi {
      ui: conrod::Ui::new(glyph_cache, theme),
      count: 0
    }
  }
}

=======================

Решение, с которым я пошел, и оно сработало (по крайней мере, пока работает). Было создать вспомогательную функцию, которая возвращала бы glyph_cache и просто использовала это. Я не уверен, что это идиоматический Rust, пока просто воспользуюсь им. Наверное, стоит привыкнуть к работе с чекером заимствований.

pub struct AppUi<'a> {
  pub ui: conrod::Ui<GlyphCache<'a>>,
  pub count: u16
}

impl<'a> AppUi<'a> {
  pub fn new() -> AppUi<'a> {

    AppUi {
      ui: conrod::Ui::new(GlyphCache::new(&get_default_font_path()).unwrap(), Theme::default()),
      count: 0
    }
  }
}

pub fn get_default_font_path() -> PathBuf {
  find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets")
    .unwrap()
    .join("FiraSans-Regular.ttf")
}

person 6D65    schedule 27.07.2015    source источник


Ответы (2)


Это потому, что &mut 10i32 недостаточно долго живет в вашей программе. Вы указали, что one будет иметь такой же срок службы, что и a, но a живет дольше, чем one, поскольку i32 выходит за пределы области действия после завершения new. С другой стороны, этот код будет работать:

#[derive(Debug)]
struct B<'a> {
    b: &'a i32
}

#[derive(Debug)]
struct A<'a> {
    one: B<'a>
}

impl<'a> A<'a> {
    fn new(x: &'a mut i32) -> A<'a> {
        // let mut b = 10i32;
        A {
            one: B{b: x}
        }
    }
}

fn main() {
    let mut x = 10i32;
    let a = A::new(&mut x);
    println!("A -> {:?}", a);
}

Обратите внимание, что x теперь живет столько же, сколько a, так что ваши жизни довольны

person Syntactic Fructose    schedule 27.07.2015
comment
Спасибо. Я понял. Хотя означает ли это, что нет способа выделить что-то в новом методе и дать ему время жизни структуры? когда new создает структуру, она продолжает жить в более высокой области видимости. не может ли время жизни полей соответствовать времени жизни структуры? Означает ли это, что все возвращаемые поля должны быть в стеке. - person 6D65; 29.07.2015
comment
@ 6D65 Вы не можете сохранить ссылку на что-то, что выходит за рамки, вы написали свою структуру так, чтобы ссылка существовала так долго, как должна. Я не понимаю, почему вы хотите создать объект в new и сохранить ссылку на него в своей структуре; вы должны переместить значение в этом случае и сохранить объект, а не ссылку - person Syntactic Fructose; 29.07.2015
comment
В другом вашем примере glyph_cache выходит за рамки, как и ваш первый пример. Вам нужно либо передать ссылку glyph_cache в new, либо не брать ссылку glyph_case и вместо этого перемещать значение - person Syntactic Fructose; 29.07.2015
comment
Вместо этого, перемещая значение, вы имеете в виду, чтобы передать glyph_cache в качестве значения в new? - person 6D65; 29.07.2015
comment
@ 6D65 да, тогда glyph_cache не выйдет из области видимости, потому что вы переместили его значение. - person Syntactic Fructose; 29.07.2015
comment
И нет способа привязать время жизни glyph_cache к времени жизни структуры? В основном, сделать кеш-глиф живым до тех пор, пока структура, даже если она создана внутри новой функции? - person 6D65; 29.07.2015
comment
@ 6D65 Не думаю, что вы понимаете. glyph_cache выходит за рамки, когда new заканчивается. Вы не можете просто сказать, что я хочу, чтобы эта переменная просуществовала так долго, независимо от области действия, вы спрашиваете, можете ли вы игнорировать правила, которые применяет ржавчина? ответ - нет, то, что вы хотите делать, в корне неверно. - person Syntactic Fructose; 29.07.2015
comment
Но что, если я сделаю glyph_cache полем в структуре. Продержится ли он столько же, сколько и структура? Затем создайте его в новом, но назначьте его полю. Я знаю, что Rust очень осторожен с областями действия, но я ожидал, что значение, выделенное в куче, может существовать до тех пор, пока у кого-то есть ссылка на него. Несмотря на то, что кэш глифов, в примере не выделяется голова. - person 6D65; 29.07.2015

Ключ к пониманию этого заключается в том, что ссылка & представляет собой заимствование, а не собственное значение. Аннотации времени жизни не влияют на продолжительность жизни значения; они отслеживают только то, что референты заимствованной ссылки переживут саму заимствованную ссылку, так что разыменование всегда будет действительным.

Заимствованная ссылка может относиться к значению в стеке (статически выделенная память) или в куче (динамически выделяемая память). Значения в стеке имеют довольно очевидный срок жизни; с момента инициализации переменной до конца блока, когда эти значения извлекаются из стека.

Значения в куче принадлежат указателям, которые живут в стеке; поэтому их время жизни определяется указателем в стеке, который им принадлежит. Однако право владения можно перемещать между разными переменными, поэтому вы можете иметь более гибкие сроки жизни этих значений, если переместите владение указателем, который ссылается на них, из одной переменной стека в другую.

Если вы напишете функцию с такой подписью:

fn new() -> A<'a> {}

вы говорите, что вы вернете A, в котором содержащиеся ссылки имеют время жизни 'a, которое определяется вызывающей стороной; но вы не можете этого сделать, поскольку вам не даются такие ссылки в качестве входных данных. Вы не можете создать значение с произвольным временем жизни ввода из функции new.

Из new функции вы обычно хотите вернуть принадлежащие значения; или, возможно, значения с заимствованиями, основанные на некоторых входных параметрах, но вам необходимо предоставить эти ссылки в качестве входных параметров.

Может помочь, если вы опишете немного больше о том, что вы пытаетесь сделать, а не просто предоставите игрушечный пример. Есть несколько возможных вещей, которые вы могли бы попытаться сделать здесь, но с помощью одного игрушечного примера трудно определить, что описать. Какова цель возврата объекта, содержащего ссылку? Разве это так, что объект может быть размещен в куче, и поэтому при его передаче нужно перемещать только один указатель, а не копировать все значение? В этом случае вам, вероятно, понадобится Box или Vec. Это так, что он может ссылаться на какую-то переменную, выделенную в стеке? Затем вам нужно выделить это в содержащем фрейме стека и передать ссылку с этим временем жизни в вашу функцию, чтобы была переменная с соответствующим временем жизни, на которую она могла бы ссылаться. Если вы просто пытаетесь вернуть объект, содержащий целое число, вы можете сделать это, указав в объекте целое число напрямую, а не ссылку на него.

person Brian Campbell    schedule 27.07.2015