
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, то можно сделать замыкание и передавать в качестве аргумента ссылку на него. В некоторых случаях сама функция требует ссылку на замыкание (хотя это, вообще говоря, странно). В таком случае можно взять ссылку непосредственно от литерала замыкания. Выглядит это несколько странно, но работает.









