Тип параметра `T` может не жить достаточно долго

Я пытаюсь написать небольшую программу на Rust, но не могу заставить ее работать.

Я воспроизвел ошибку в меньшем сценарии:

fn main() {
    let name = String::from("World");
    let test = simple(name);
    println!("Hello {}!", test())
}

fn simple<T>(a: T) -> Box<Fn() -> T> {
    Box::new(move || -> T {
        a
    })
}

Когда я компилирую это, я получаю эту ошибку:

error[E0310]: the parameter type `T` may not live long enough
  --> test.rs:8:9
   |
7  |       fn simple<T>(a: T) -> Box<Fn() -> T> {
   |                 - help: consider adding an explicit lifetime bound `T: 'static`...
8  | /         Box::new(move || -> T {
9  | |             a
10 | |         })
   | |__________^
   |
note: ...so that the type `[[email protected]:8:18: 10:10 a:T]` will meet its required lifetime bounds
  --> test.rs:8:9
   |
8  | /         Box::new(move || -> T {
9  | |             a
10 | |         })
   | |__________^

Я попытался добавить явную оценку времени жизни T: 'static как предполагает ошибка, но я получаю новую ошибку:

error[E0507]: cannot move out of captured outer variable in an `Fn` closure
 --> test.rs:9:13
  |
7 |     fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
  |                           - captured outer variable
8 |         Box::new(move || -> T {
9 |             a
  |             ^ cannot move out of captured outer variable in an `Fn` closure

2 ответа

Решение

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

Во-первых, simple функция должна указать время жизни для ее T параметр. С точки зрения функции, T может быть любого типа, что означает, что он может быть ссылкой, поэтому он должен иметь время жизни. Пожизненное разрешение не относится к этому случаю, поэтому вам нужно записать его явно. Компилятор предлагает 'static, что хорошо для Привет мира. Если бы у вас были более сложные времена жизни, вам нужно было бы использовать параметр времени жизни; см. мой пример ниже для большего.

Ваше закрытие не может быть Fn потому что вы не можете назвать это более одного раза. Как говорится в новой ошибке, ваше закрытие перемещает полученное значение (a) из закрытия, когда это называется. Это то же самое, что сказать, что это метод, который требует self вместо &self, Если бы вызовы функций были обычным методом вместо специального синтаксиса, это было бы что-то вроде этого:

trait FnOnce {
    type Output
    fn call(self) -> Output
}

trait Fn : FnOnce {
    fn call(&self) -> Output
}

// generated type
struct MyClosure<T> {
    a: T
}

impl<T> FnOnce for MyClosure<T> {
    fn call(self) -> T { self.a }
}

(Это не намного проще, чем фактические определения этих типов.)

Короче говоря, замыкание, которое использует свои захваченные значения, не реализует Fn, только FnOnce, Вызов это потребляет закрытие. Там также есть FnMut но это здесь не актуально.

Это имеет другое значение, связанное с потреблением значений при их перемещении. Вы могли заметить, что вы не можете вызвать метод, который принимает self на любой признак объекта (Box<T> где T это черта). Чтобы переместить объект, код, который его перемещает, должен знать размер перемещаемого объекта. Этого не происходит с чертами объектов, которые не являются размерами. Это также относится к Box<FnOnce>, Поскольку вызов замыкания перемещает его (потому что вызов self method`), вы не можете вызвать замыкание.

Так как же обойти эту проблему? Это делает Box<FnOnce> немного бесполезно. Есть два варианта.

Если вы можете использовать нестабильную Rust, вы можете использовать FnBox тип: это замена для FnOnce который работает внутри Box, Он скрыт за воротами функций, потому что, как вас предупреждает документация: "Обратите внимание, что FnBox может быть устаревшим в будущем, если Box<FnOnce()> замыкания становятся непосредственно пригодными для использования. " Вот игровая площадка, которая использует это решение и добавляет параметры времени жизни для решения исходной проблемы.

Альтернативой, которая могла бы быть более широко применимым инженерным решением, было бы избегать выхода из затвора.

  • Вы можете вернуть ссылку &'static T если вы всегда помещаете статические объекты в замыкание. Таким образом, вы можете вызывать замыкание столько раз, сколько захотите, и все вызывающие получат ссылку на один и тот же объект.

  • Если объект не является статичным, вы можете вместо этого вернуть Rc<T>, В этом случае все вызывающие абоненты по-прежнему получают ссылку на один и тот же объект, и время жизни этого объекта управляется динамически, поэтому он будет оставаться в живых столько, сколько потребуется. Вот еще одна площадка, реализующая эту опцию.

  • Вы можете сделать так, чтобы замыкание копировало свой аргумент каждому вызывающему. Таким образом, он может вызываться столько раз, сколько необходимо, и каждый вызывающий получит свою собственную копию. Никакого дальнейшего управления жизненным циклом не потребуется. Если вы реализуете это таким образом, вы все равно можете сделать аргумент Rc<T> вместо T использовать функцию так же, как и выше.

simple функция возвращает замыкание, которое является общим для возвращаемого типа T,

Это подразумевает, что возвращаемый тип может быть чем угодно, например ссылкой или типом, содержащим ссылки, поэтому компилятор предлагает указать 'static по типу:

fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
    Box::new(move || -> T { a })
}

но теперь у вас есть проблема:

error[E0507]: cannot move out of captured outer variable in an `Fn` closure
 --> src/main.rs:2:29
  |
1 | fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
  |                       - captured outer variable
2 |     Box::new(move || -> T { a })
  |                             ^ cannot move out of captured outer variable in an `Fn` closure

error[E0597]: `name` does not live long enough
 --> src/main.rs:7:24
  |
7 |     let test = simple(&name);
  |                        ^^^^ borrowed value does not live long enough
8 |     println!("Hello {}!", test())
9 | }
  | - borrowed value only lives until here
  |
  = note: borrowed value must be valid for the static lifetime...

Потому что захваченная переменная name принадлежит внешнему "основному" контексту, он не может быть "украден" кем-то другим.

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

В штучной упаковке реализация Fn Черта живет в куче, и правильное время жизни должно быть явно назначено: Fn() -> &'a T` + 'a

fn main() {
    let name = String::from("World");
    let test = simple(&name);
    println!("Hello {}!", test())
}

fn simple<'a, T: 'a>(val: &'a T) -> Box<Fn() -> &'a T + 'a> {
    Box::new(move || -> &'a T { val })
}

Другое решение заключается в использовании impl trait, доступного с Rust 1.26:

fn main() {
    let name = String::from("World");
    let test = simple(&name);
    println!("Hello {}!", test())
}

fn simple<'a, T: 'a>(val: &'a T) -> impl Fn() -> &'a T {
    move || -> &'a T { val }
}
Другие вопросы по тегам