
Tckb ds rjulf-yb,elm gbcfkb d nthvbyfkt
git
dvtcnj пше
, nj, djpvj;yj, dfv ghbujlbncz 'nf ghjuhfvvf, rjnjhfz fdnjvfnbxtcrb bcghfdkztn zpsr yf ghfdbkmysq.github.com/danakt/pshe
Size: a a a
git
dvtcnj пше
, nj, djpvj;yj, dfv ghbujlbncz 'nf ghjuhfvvf, rjnjhfz fdnjvfnbxtcrb bcghfdkztn zpsr yf ghfdbkmysq.git
dvtcnj пше
, nj, djpvj;yj, dfv ghbujlbncz 'nf ghjuhfvvf, rjnjhfz fdnjvfnbxtcrb bcghfdkztn zpsr yf ghfdbkmysq.fn main() {Кстати, на замыкания это тоже распространяется: значение замыкания хранит в себе только захваченные данные, поэтому размер замыкания равен (с поправкой на выравнивание) размеру захваченных данных. Если замыкание ничего не захватывает, то у него нулевой размер:
assert_eq!(std::mem::size_of_val(&add_one), 0);
let add_one_ptr: fn(u32) -> u32 = add_one;
assert_eq!(
std::mem::size_of_val(&add_one_ptr),
std::mem::size_of::<usize>()
);
}
fn main() {Теперь я вижу, что в этом действительно есть смысл. И всё-таки, как скомпилировать тот пример с парой функций?
assert_eq!(std::mem::size_of_val(&|x: u32| x + 1), 0);
}
let funcs: Pair<fn(_) -> _> = (add_one, add_two);Обратите внимание, выписывать тип точно в этом случае не нужно, достаточно сказать, что это function pointer. Другой способ — явно привести одно из значений в паре:
let funcs: Pair<_> = (add_one as fn(_) -> _, add_two);Второе значение будет неявно приведено (coerced) к нужному типу. В принципе, ничто не мешает написать явный каст у обоих значений, но надобности в этом в данном примере нет. Кстати, если мы сделаем массив функций, то никаких кастов делать не придётся, rustc сам уравняет типы до функционального указателя:
let arr = [add_one, add_two];А что, если замыкание возвращает захваченное значение? Если это значение не копируемо, то замыкание можно вызвать только один раз...
core::ops::{
FnOnce, FnMut, Fn}
.self
, &mut self
или &self
(пошёл нафиг, arbitrary self types!). Соответственно и определения у Fn*-трейтов отличаются. Вот как они выглядят:trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
Ти́повый параметр Args
в сгенерированных реализациях — это кортеж, элементы которого — типы аргументов, по одному на каждый аргумент. Ассоциированный тип Output
— это тип значения, возвращаемого при вызове замыкания. Методы трейтов объявлены с соглашением о вызове rust-call
. Это, в частности, означает, что их нельзя сохранить или передать там, где ожидаются обычные функции.call
трейта FnOnce
принимает замыкание по значению, т. е. при вызове замыкание перемещается и его после этого нельзя использовать. Этот трейт не имеет никаких ограничений, что логично: если у нас есть замыкание, то его всегда можно вызвать как минимум один раз. В принципе, замыкание может не реализовывать другие трейты:struct A;
let a = A;
let closure = || a;
closure();
// closure(); // use of moved value: closure
Здесь мы объявляем тип значение a
типа A
, которое не является копируемым, и замыкание, которое это значение захватывает и возвращает при вызове. Замыкания в Rust, если не указано ключевое слово move
, захватывают значение по ссылке (возможно, мутабельной). Здесь a
возвращается, поэтому его можно захватить только по значению. Так как это значение не копируемо, после вызова это значение перемещается из структуры данных, реализующей замыкание, и более замыкание вызвать нельзя. Собственно, это нам и скажет компилятор, если мы расскомментируем последную строку.FnMut
имеет в качестве супертрейта FnOnce
— как я уже сказал, любое замыкание можно вызвать как минимум раз. Метод этого трейта call_mut
принимает &mut self
, а потому замыкание, во-первых, можно вызвать несколько раз, а во-вторых, может менять захваченные значения. Вот пример:let mut n = 0;
let mut counter = || {
n += 1;
n
};
println!("{}", counter()); // 1
println!("{}", counter()); // 2
println!("{}", counter()); // 3
Здесь мы сделали переменную счётчик n
и замыкание, которое его инкрементирует при каждом вызовом перед тем, как вернуть. Что тут важно — в силу того, что call_mut
требует &mut self
, а мутабельную ссылку можно взять только от мутабельного значения, нам пришлось объявить само замыкание как мутабельное. Если этот mut
опустить, то компилятор резонно откажется компилировать код.call
принимает значение по ссылке и потому замыкание можно вызывать несколько раз, но оно не может менять захваченные значения?Fn
является субтрейтом FnMut
, и это тоже логично: если мы можем вызвать метод по разделяемой ссылке, то можем вызвать и по уникальной. Надо только иметь в виду, что &mut
в Rust вообще-то означает не изменяемость, а уникальность, поэтому из-за interior mutability пример со счётчиком можно переделать и с "иммутабельным" замыканием.core::ops::{
FnOnce, FnMut, Fn}
.self
, &mut self
или &self
(пошёл нафиг, arbitrary self types!). Соответственно и определения у Fn*-трейтов отличаются. Вот как они выглядят:trait FnOnce<Args> {
type Output;
extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
pub trait Fn<Args>: FnMut<Args> {
extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
Ти́повый параметр Args
в сгенерированных реализациях — это кортеж, элементы которого — типы аргументов, по одному на каждый аргумент. Ассоциированный тип Output
— это тип значения, возвращаемого при вызове замыкания. Методы трейтов объявлены с соглашением о вызове rust-call
. Это, в частности, означает, что их нельзя сохранить или передать там, где ожидаются обычные функции.call
трейта FnOnce
принимает замыкание по значению, т. е. при вызове замыкание перемещается и его после этого нельзя использовать. Этот трейт не имеет никаких ограничений, что логично: если у нас есть замыкание, то его всегда можно вызвать как минимум один раз. В принципе, замыкание может не реализовывать другие трейты:struct A;
let a = A;
let closure = || a;
closure();
// closure(); // use of moved value: closure
Здесь мы объявляем тип значение a
типа A
, которое не является копируемым, и замыкание, которое это значение захватывает и возвращает при вызове. Замыкания в Rust, если не указано ключевое слово move
, захватывают значение по ссылке (возможно, мутабельной). Здесь a
возвращается, поэтому его можно захватить только по значению. Так как это значение не копируемо, после вызова это значение перемещается из структуры данных, реализующей замыкание, и более замыкание вызвать нельзя. Собственно, это нам и скажет компилятор, если мы расскомментируем последную строку.FnMut
имеет в качестве супертрейта FnOnce
— как я уже сказал, любое замыкание можно вызвать как минимум раз. Метод этого трейта call_mut
принимает &mut self
, а потому замыкание, во-первых, можно вызвать несколько раз, а во-вторых, может менять захваченные значения. Вот пример:let mut n = 0;
let mut counter = || {
n += 1;
n
};
println!("{}", counter()); // 1
println!("{}", counter()); // 2
println!("{}", counter()); // 3
Здесь мы сделали переменную счётчик n
и замыкание, которое его инкрементирует при каждом вызовом перед тем, как вернуть. Что тут важно — в силу того, что call_mut
требует &mut self
, а мутабельную ссылку можно взять только от мутабельного значения, нам пришлось объявить само замыкание как мутабельное. Если этот mut
опустить, то компилятор резонно откажется компилировать код.call
принимает значение по ссылке и потому замыкание можно вызывать несколько раз, но оно не может менять захваченные значения?Fn
является субтрейтом FnMut
, и это тоже логично: если мы можем вызвать метод по разделяемой ссылке, то можем вызвать и по уникальной. Надо только иметь в виду, что &mut
в Rust вообще-то означает не изменяемость, а уникальность, поэтому из-за interior mutability пример со счётчиком можно переделать и с "иммутабельным" замыканием.Fn(i32) -> i32
, то можно сделать замыкание и передавать в качестве аргумента ссылку на него. В некоторых случаях сама функция требует ссылку на замыкание (хотя это, вообще говоря, странно). В таком случае можно взять ссылку непосредственно от литерала замыкания. Выглядит это несколько странно, но работает.