Тип параметра `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 }
}