Ответ 1
Очень интересный вопрос! Я думаю, что я понял проблему в игре здесь. Позвольте мне попытаться объяснить.
Замыкания tl; dr: move
не могут возвращать ссылки на свое окружение, поскольку это будет ссылка на self
. Такая ссылка не может быть возвращена, потому что черты Fn*
не позволяют нам выразить это. Это в основном то же самое, что и проблема потокового итератора, и может быть исправлено с помощью GAT (общих связанных типов).
Реализуя это вручную
Как вы, вероятно, знаете, когда вы пишете замыкание, компилятор генерирует структуру и блоки impl
для соответствующих признаков Fn
, поэтому замыкания в основном являются синтаксическим сахаром. Давайте попробуем избежать всего этого сахара и создадим ваш тип вручную.
Вам нужен тип, который владеет другим типом и может возвращать ссылки на этот принадлежащий тип. И вы хотите иметь функцию, которая возвращает упакованный экземпляр указанного типа.
struct Baz<T>(T);
impl<T> Baz<T> {
fn call(&self) -> &T {
&self.0
}
}
fn make_baz<T>(t: T) -> Box<Baz<T>> {
Box::new(Baz(t))
}
Это довольно эквивалентно вашему закрытию в штучной упаковке. Давайте попробуем использовать это:
let outside = {
let s = "hi".to_string();
let baz = make_baz(s);
println!("{}", baz.call()); // works
baz
};
println!("{}", outside.call()); // works too
Это работает просто отлично. Строка s
перемещается в тип Baz
а этот экземпляр Baz
перемещается в Box
. Теперь s
принадлежит baz
а затем outside
.
Это становится более интересным, когда мы добавляем один символ:
let outside = {
let s = "hi".to_string();
let baz = make_baz(&s); // <-- NOW BORROWED!
println!("{}", baz.call()); // works
baz
};
println!("{}", outside.call()); // doesn't work!
Теперь мы не можем сделать время жизни baz
больше, чем время жизни s
, так как baz
содержит ссылку на s
которая будет висящей ссылкой на s
выйдет из области видимости раньше, чем baz
.
Смысл этого фрагмента, который я хотел бы подчеркнуть: нам не нужно было аннотировать время жизни типа Baz
чтобы сделать это безопасным; Руст понял это сам и убедился, что baz
живет не дольше, чем s
. Это будет важно ниже.
Написание черты для этого
Пока мы только охватили основы. Давайте попробуем написать такую черту, как Fn
чтобы приблизиться к исходной проблеме:
trait MyFn {
type Output;
fn call(&self) -> Self::Output;
}
В нашей характеристике нет никаких параметров функции, но в остальном она довольно идентична реальной характеристике Fn
.
Давай реализуем это!
impl<T> MyFn for Baz<T> {
type Output = ???;
fn call(&self) -> Self::Output {
&self.0
}
}
Теперь у нас есть проблема: что мы пишем вместо ???
? Наивно можно было бы написать &T
... но нам нужен параметр времени жизни для этой ссылки. Где взять? Какое время жизни у возвращаемого значения?
Давайте проверим функцию, которую мы реализовали ранее:
impl<T> Baz<T> {
fn call(&self) -> &T {
&self.0
}
}
Поэтому здесь мы также используем &T
без параметра времени жизни. Но это работает только из-за жизненного выбора. По сути, компилятор заполняет пробелы, так что fn call(&self) → &T
эквивалентен:
fn call<'s>(& self) -> & T
Ага, так что время жизни возвращенной ссылки связано с временем жизни самого self
! (более опытные пользователи Rust могут уже чувствовать, куда это идет...).
(В качестве примечания: почему возвращаемая ссылка не зависит от времени жизни самого T
? Если T
ссылается на что-то non- 'static
то это необходимо учитывать, верно? Да, но это уже учитывается! Помните, что ни одного случая Baz<T>
может когда - нибудь жить дольше, чем вещь T
может ссылаться. Так что self
время жизни уже короче, чем то, что срок службы T
может иметь. Таким образом, нам нужно только сосредоточиться на self
всю жизнь)
Но как мы можем выразить это в чертах характера? Оказывается: мы не можем (пока). Эта проблема регулярно упоминается в контексте потоковых итераторов, то есть итераторов, которые возвращают элемент со временем жизни, привязанным к self
жизни. В сегодняшнем Русте, к сожалению, это невозможно реализовать; система типов недостаточно сильна.
Как насчет будущего?
К счастью, существует RFC "Generic Associated Types", который был объединен некоторое время назад. Этот RFC расширяет систему типов Rust, позволяя ассоциированным типам черт быть универсальными (в течение других типов и времени жизни).
Давайте посмотрим, как мы можем заставить ваш пример (вроде) работать с GAT (согласно RFC; этот материал пока не работает ☹). Сначала мы должны изменить определение черты:
trait MyFn {
type Output<'a>; // <-- we added <'a> to make it generic
fn call(&self) -> Self::Output;
}
Сигнатура функции в коде не изменилась, но обратите внимание на то, что время жизни возрастает! Вышеупомянутый fn call(&self) → Self::Output
эквивалентен:
fn call<'s>(& self) -> Self::Output<'s>
Таким образом, время жизни ассоциированного типа связано с временем жизни самого self
. Как мы и хотели! impl
выглядит так:
impl<T> MyFn for Baz<T> {
type Output<'a> = &'a T;
fn call(&self) -> Self::Output {
&self.0
}
}
Чтобы вернуть упакованный MyFn
нам нужно написать это (в соответствии с этим разделом RFC:
fn make_baz<T>(t: T) -> Box<for<'a> MyFn<Output<'a> = &'a T>> {
Box::new(Baz(t))
}
А что, если мы хотим использовать реальную черту Fn
? Насколько я понимаю, мы не можем, даже с GAT. Я думаю, что невозможно изменить существующую черту Fn
для использования GAT обратно совместимым образом. Поэтому вполне вероятно, что стандартная библиотека сохранит менее мощную черту как есть. (примечание: как развивать стандартную библиотеку несовместимыми способами использования новых языковых функций - это то, о чем я уже несколько раз задумывался; пока я не слышал ни о каком реальном плане в этом отношении; я надеюсь, что команда Rust придет с чем-то...)
Резюме
То, что вы хотите, не является технически невозможным или небезопасным (мы реализовали его как простую структуру, и она работает). Однако, к сожалению, сейчас невозможно выразить то, что вы хотите, в виде замыканий/черт Fn
в системе типов Rust. С этой же проблемой сталкиваются потоковые итераторы.
С запланированной функцией GAT можно выразить все это в системе типов. Тем не менее, стандартная библиотека должна была бы как-то наверстать упущенное, чтобы сделать возможным ваш точный код.