Возвращение замыкания из функции
Примечание: этот вопрос был задан до первой стабильной версии Rust. С тех пор было много изменений, и синтаксис, используемый в функции, даже больше не действителен. Тем не менее, ответ Shepmaster превосходен и делает этот вопрос достойным внимания.
Наконец приземлились закрытые крышки, поэтому я экспериментирую с ними, чтобы увидеть, что вы можете сделать.
У меня есть эта простая функция:
fn make_adder(a: int, b: int) -> || -> int {
|| a + b
}
Тем не менее я получаю missing lifetime specifier [E0106]
об ошибке missing lifetime specifier [E0106]
. Я попытался исправить это, изменив тип возвращаемого значения на ||: 'static → int
, но затем я получил еще одну ошибку, cannot infer an appropriate lifetime due to conflicting requirements
Если я правильно понимаю, закрытие распаковано, поэтому он владеет a
и b
. Мне кажется очень странным, что это нужно на всю жизнь. Как я могу это исправить?
Ответы
Ответ 1
Начиная с Rust 1.26, вы можете использовать impl trait
:
fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
move |b| a + b
}
fn main() {
println!("{}", make_adder(1)(2));
}
Это позволяет вернуть распакованное закрытие, даже если невозможно указать точный тип закрытия.
Это не поможет вам, если что-то из этого верно:
-
Вы нацелены на Rust до этой версии
-
У вас есть какие-то условия в вашей функции:
fn make_adder(a: i32) -> impl Fn(i32) -> i32 {
if a > 0 {
move |b| a + b
} else {
move |b| a - b
}
}
Здесь нет единственного возвращаемого типа; каждое закрытие имеет уникальный, не подлежащий определению тип.
-
Вы должны быть в состоянии назвать возвращаемый тип по любой причине:
struct Example<F>(F);
fn make_it() -> Example<impl Fn()> {
Example(|| println!("Hello"))
}
fn main() {
let unnamed_type_ok = make_it();
let named_type_bad: /* No valid type here */ = make_it();
}
Вы не можете (пока) использовать impl SomeTrait
в качестве типа переменной.
В этих случаях вам нужно использовать косвенное обращение. Общее решение - это объект черты, как описано в другом ответе.
Ответ 2
Можно вернуть замыкания внутри Box
es, т.е. как объекты объектов, реализующие определенный признак:
fn make_adder(a: i32) -> Box<Fn(i32) -> i32> {
Box::new(move |b| a + b)
}
fn main() {
println!("{}", make_adder(1)(2));
}
(попробуйте здесь)
Существует также RFC (его проблема отслеживания) при добавлении распакованных абстрактных типов возврата, которые позволяли бы возвращать закрытие по значению без полей, но этот RFC был отложен. Согласно обсуждению в RFC, кажется, что в последнее время в нем делается некоторая работа, поэтому вполне возможно, что типы нежелательных абстрактных возвратов будут доступны относительно скоро.
Ответ 3
Синтаксис ||
по-прежнему остается старым закрытым ящиком, поэтому это не работает по той же причине, что и раньше.
И он не будет работать даже с использованием правильного синтаксиса закрытия коробки |&:| -> int
, так как это буквально просто сахар для определенных черт. В настоящий момент синтаксис сахара |X: args...| -> ret
, где X
может быть &
, &mut
или ничего, что соответствует Fn
, FnMut
, FnOnce
вы можете также написать Fn<(args...), ret>
и т.д. для несахарной формы. Сахар, вероятно, будет меняться (возможно, что-то вроде Fn(args...) -> ret
).
Каждое незаблокированное закрытие имеет уникальный, невосприимчивый тип, сгенерированный внутри компилятором: единственный способ поговорить о распакованных закрываниях - через общие и ограниченные границы. В частности, запись
fn make_adder(a: int, b: int) -> |&:| -> int {
|&:| a + b
}
похож на запись
fn make_adder(a: int, b: int) -> Fn<(), int> {
|&:| a + b
}
то есть. говоря, что make_adder
возвращает значение unboxed trait; что на данный момент не имеет особого смысла. Первое, что нужно попробовать:
fn make_adder<F: Fn<(), int>>(a: int, b: int) -> F {
|&:| a + b
}
но это говорит о том, что make_adder
возвращает любой F
, который выбирает вызывающий, в то время как мы хотим сказать, что он возвращает некоторый фиксированный (но "скрытый" ) тип. Для этого требуются абстрактные типы возврата, в котором говорится, в основном, "возвращаемое значение реализует этот признак", все еще будучи разобранным и статически разрешенным. На языке этого (временно закрытого) RFC,
fn make_adder(a: int, b: int) -> impl Fn<(), int> {
|&:| a + b
}
Или с закрытием сахара.
(Еще одна второстепенная точка: я не уверен на 100% в отношении незакрытых закрытий, но старые закрытия, безусловно, все еще захватывают вещи по ссылке, что является другой вещью, которая поглощает код, как предлагалось в этой проблеме. Это исправляется в # 16610.)