Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

Rust 🦀


📌 Создать новую структуру в Tab:

  • 👾 Best practice
  • Варианты применения
  • Добавить возможность вставки WebGPU
  • страница ссылок из вкладок браузера

👾 Теперь нет смысла собирать примеры методов, с LLMs можно актуальные реальные примеры применения рассматривать До LLM был смысл в сборе примеров так как документация не имела нужных примеров ведь она имеет свою структуру и сбор подходов и объяснений на сайтах типа medium... НО теперь LLM может тебе объяснит все по полочкам с примерами. Теперь нужно с LLM пройтись по темам и собрать общее понимание подходов и библиотек с перечнем методов чего они могут. Например, тема асинхронного программирования довольно запутана, ее можно расписать по полочкам и т.д. все темы.

Когда вы затеняете переменную новой переменной с тем же именем, вы не уничтожаете первую. Вы ее блокируете
Связывание
  • Неизменяемое (по умолчанию) связывание (Оператор объявления) имен, переменная с шаблоном
fn main(){
    let x = 9; 
    let (x,y) = (9,5);
}
  • Изменяемое связывание модификатор mut
fn main(){
    let (mut x,mut y) = (5, 6);
    x = 10;
    y = 11;
}
`raw` идентификаторы (`r#`) экранируют зарезервированные ключевые слова

raw identifiers

Использование зарезервированных ключевых идентификаторов языка
extern crate foo;

fn main() {
  //  foo::try(); // Error try это ключевое слово языка
      foo::r#try();
}
Затерли переменную новой
  • Затенение переменной
fn main() {
    let my_number = 8;
    println!("{}", my_number); // 8
    {
        let my_number = 9.2;
        println!("{}", my_number);  // 9.2
    }
    println!("{}", my_number); // 8
}
  • Затирание переменной новой
fn main(){
    let x: i32 = 8;
    println!("{}", x); // Выводит 8
    let x =  42;
    println!("{}", x); // Выводит 42
}
Исходная переменная всё ещё существует до тех пор, пока есть ссылки на неё.
fn main(){
    let country = String::from("Austria");
    let country_ref = &country;
    let country = 8;
    println!("{country_ref} {country}");// Austria 8 
    // т.е. первая переменная country не удалилась, а затенилась раз ее ссылка country_ref и показывает ее значение 
}

Единственный способ вернуть образно затененную переменную это восстановить ее по ее ссылке let country:String = country_ref.to_string();

Затенение позволяет связать имя с другим типом или изменить связь с изменяемой на неизменяемую.

Затенение позволяет связать имя с другим типом или изменить связь с изменяемой на неизменяемую.

fn main(){
    let mut x: i32 = 1;
    x = 7;
    let x = x; // теперь x неизменяемое и связанно с 7
}
Есть два типа операторов в Rust: «операторы объявления» и «операторы выражения». Все остальное — выражения. Выражение возвращает значение, в то время как оператор - нет

{} — выражение. Его цель - превратить любое выражение в оператор.

1 + { let x = 2; x * 2 }

Блок состоит из инструкций (statement), завершённых.

Значение блока — значение хвостового выражения.

fn main(){
    let i: i32 = { 1 };

    let i: () = { 1; };
}

Точки с запятой имеют значение!

Связанные имена имеют область видимости

Переменные ограничены блоком, в котором они были объявлены. Блок — это код в { }

fn main(){
    let x: i32 = 17; 
    {
         let y: i32 = 3;
         println!(""Значение x равно {} и значение y равно {}"", x, y);
    }
    println!(" {}",  y); // Ошибка компиляции
}
Применение

Лишние переменные не видны снаружи

fn main(){
    let x = 5u32;

    let y = {
        let x_squared = x * x;
        let x_cube = x_squared * x;

        // Результат этого выражение будет присвоен переменной `y`
        x_cube + x_squared + x
    };

    let z = {
        // Т.к это выражение оканчивается на `;`, переменной `z` будет присвоен `()`
        2 * x;
    };
}
Есть вкладка по работе с асинхронным вводом/выводом Futures, Асинхронный ввод-вывод, Actors, async/await and runtime, Module std::process

Чтение и запись в std::io::stdout

Чтение с консоли — stdin()

use std::io::Write;
fn main(){
   let mut line = String::new();
   println!("Enter your name :");
   let b1 = std::io::stdin().read_line(&mut line).unwrap();
   println!("Hello , {}", line);
   println!("no of bytes read , {}", b1);
}

Запись в консоль — stdout()

use std::io::Write; // (stdout реализует трейт Write)
fn main(){
    let b1 = std::io::stdout().write("Tutorials ".as_bytes()).unwrap();
    let b2 = std::io::stdout().write(String::from("Point").as_bytes()).unwrap();
    std::io::stdout().write(format!("\nbytes written {}",(b1+b2)).as_bytes()).unwrap();
}

Запись в консоль — stderr()

use std::io::Write; // (stderr реализует трейт Write)
fn main() -> std::io::Result<()> {
    //   eprintln!("{}","error msg");
    std::io::stderr().write_all(b"error msg")?;
   /*
    let name = "Shoot";
    let r = writeln!(&mut std::io::stderr(), "{}", name);
    r.expect("failed printing to stderr");
   */
    Ok(())
}

Чтение std::io::stdout в файл

fn main(){
    std::io::stdout().write("Shoot3\n".as_bytes()).unwrap();
}

Запуск:

$ cargo run > out_stderr.txt

Использование явной синхронизации:

use std::io::{self, Write};
fn main() -> io::Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    handle.write_all(b"hello world")?;
    Ok(())
}

Чтение и запись в std::io::stdout

Запись в консоль — stdout()

use std::io::Write; // (stdout реализует трейт Write)
fn main(){
    let b1 = std::io::stdout().write("Tutorials ".as_bytes()).unwrap();
    let b2 = std::io::stdout().write(String::from("Point").as_bytes()).unwrap();
    std::io::stdout().write(format!("\nbytes written {}",(b1+b2)).as_bytes()).unwrap();

   writeln!(std::io::stdout(), "{}", "hello");
}

Чтение в файл

fn main(){
   std::io::stdout().write("Shoot3\n".as_bytes()).unwrap();
}

Запуск:

$ cargo run > out_stderr.txt

Использование явной синхронизации:

use std::io::{self, Write};
fn main() -> io::Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    handle.write_all(b"hello world")?;
    Ok(())
}

Чтение и запись в std::io::stderr

Вывод/печать ошибок в стандартный поток ошибок STDERR через eprintln!

fn main(){
   eprintln!("Error: arguments --conf can not be empty !");     
  
   write!(&mut io::stderr(), "{}", "Error: arguments --conf can not be empty !");

   process::exit(1);
}

Запись в консоль — stderr()

use std::io::Write; (stderr реализует трейт Write)

fn main() -> std::io::Result<()> {
    // eprintln!("{}","error msg");
    std::io::stderr().write_all(b"error msg")?;
   
    let name = "Shoot";
    let r = writeln!(&mut std::io::stderr(), "{}", name);
    r.expect("failed printing to stderr");
    Ok(())
}
Перенаправить `stderr` к `stdout`
`cargo test` пишет ошибки в `stderr`, так что вы должны перенаправить `stderr` к `stdout` следующим образом:

cargo test --color always 2>&1 | less -r

вывод первой ошибки
cargo test --color always 2>&1 | grep error| head -1

первой ошибки 20 строк кода:
cargo test --color always 2>&1 | grep error -A 20| head -20

cargo test --color always 2>&1 | less -r |grep error| head -1
один аргумент через пробел
fn main() {
    use std::io::{self, stdin, stdout, Write};
    let mut str_numbers = String::new();
    io::stdin().read_line(&mut str_numbers );
    let mut iterator = str_numbers.split(" ");
    let n1: &i32 = &iterator.next().unwrap_or("0").trim().parse::<i32>().unwrap_or(0);
    let n2: &i32 = &iterator.next().unwrap_or("0").trim().parse::<i32>().unwrap_or(0);
  /* let mut buffer = String::new();
   {
        use std::fmt::Write;
        write!(buffer, "{}", n1 + n2);
    } */   
    let  buffer  = format!("{}",format_args!("{}",n1 + n2));
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    handle.write(buffer.as_bytes());
}

Можно проще:

use std::io::Write;
writeln!(std::io::stdout(), "{}", "hello");
Пример склеивания `stdout` и `stderr`
fn exec_command_yarn_test(
    path_yarn: Option<&PathBuf>,
    path_dir: &PathBuf,
    path_file: &PathBuf,
) -> Result<(), Error> {
    let command = {
        if path_yarn.is_none() {
            PathBuf::from("yarn")
        } else {
            path_yarn.unwrap().to_path_buf()
        }
    };

    #[cfg(target_os = "windows")]
    let output = {
        let path_file = path_file
            .to_string_lossy()
            .replace(r"\\", "/")
            .replace("\\", "/");
        Command::new("cmd")
            .current_dir(path_dir)
            .arg("/C")
            .arg(command)
            .arg("test")
            .arg(path_file)
            .arg("--passWithNoTests")
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .output()
            .map_err(|e| Error::YarnExecute(e))?
    };

    #[cfg(not(target_os = "windows"))]
    let output = Command::new(command)
        .current_dir(path_dir)
        .arg("test")
        .arg(path_file)
        .arg("--passWithNoTests")
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .map_err(|e| Error::YarnExecute(e))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(Error::YarnWrongSetting(stderr.into()));
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    let mut combined_output = String::new();
    combined_output.push_str(&stdout);
    combined_output.push_str(&stderr);
    print_success_result(combined_output.as_bytes());

    Ok(())
}
fn print_success_result(result: &[u8]) -> Result<(), Error> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();

    handle.write_all(&result).map_err(|e| Error::IoError(e))?;
    handle.flush().map_err(|e| Error::IoError(e))?;
    Ok(())
}

fn print_failure_result(err: Error) -> Result<(), Error> {
    let stderr = io::stderr();
    let mut handle = stderr.lock();

    writeln!(handle, "{}", err).map_err(|e| Error::IoError(e))?;
    handle.flush().map_err(|e| Error::IoError(e))?;
    Ok(())
}
pub fn wrap_exec_command_yarn_test(
    path_yarn: Option<&PathBuf>,
    path_dir: &PathBuf,
    path_file: &PathBuf,
) {
    if let Err(e) = exec_command_yarn_test(path_yarn, path_dir, path_file) {
        // Sends Error data to the io::stderr output stream
        print_failure_result(e);
    }
}
fn main(){}

Pipes

Структура std::Child представляет собой запущенный дочерний процесс и предоставляет дескрипторы stdin, stdout и stderr для взаимодействия с этим процессом через каналы (pipes)

rust-by-example/pipe

fn.pipe

use std::io::prelude::*;
use std::process::{Command, Stdio};

static PANGRAM: &'static str =
"the quick brown fox jumped over the lazy dog\n";

fn main() {
    // Создадим команду `wc`
    let process = match Command::new("wc")
                                .stdin(Stdio::piped())
                                .stdout(Stdio::piped())
                                .spawn() {
        Err(why) => panic!("не удалось создать wc: {}", why.description()),
        Ok(process) => process,
    };

    // Запишем строку в `stdin` созданной команды.
    //
    // `stdin` имеет тип `Option<ChildStdin>`, но так как мы знаем, что экземпляр должен быть только один,
    // мы можем напрямую вызвать `unwrap`.
    match process.stdin.unwrap().write_all(PANGRAM.as_bytes()) {
        Err(why) => panic!("не удалось записать в stdin команды wc: {}", why),
        Ok(_) => println!("пангамма отправлена"),
    }

    // Так как `stdin` не существует после вышележащих вызовов, он разрушается
    // и канал закрывается.
    //
    // Это очень важно, иначе `wc` не начал бы обработку только что
    // отправленных данных.

    // Поле `stdout` имеет тип `Option<ChildStdout>` и может быть извлечено.
    let mut s = String::new();
    match process.stdout.unwrap().read_to_string(&mut s) {
        Err(why) => panic!("невозможно прочесть stdout команды wc: {}", why),
        Ok(_) => print!("wc ответил:\n{}", s),
    }
}

Если вы хотите дождаться завершения process::Child, вы должны вызвать Child::wait, который вернёт process::ExitStatus

use std::process::Command;
fn main() {
    let mut child = Command::new("sleep").arg("5").spawn().unwrap();
    let _result = child.wait().unwrap();

    println!("достигнут конец функции main");
}
  • format! записывает форматированный текст в String.
  • print! работает аналогично с format!, но текст выводится в консоль (io::stdout).
  • println! аналогично print!, но в конце добавляется переход на новую строку.
  • stringify! форматированный вывод без Debug
  • eprint! аналогично format!, но текст выводится в стандартный поток ошибок (io::stderr).
  • eprintln! аналогично eprint!, но в конце добавляется переход на новую строку.
  • write! макрос вывод в буфер
  • lazy_format! избегает выделения памяти в куче.
  • format_args! возвращает форматированую строку без выделения пямяти в куче
  • concat! макрос объединяет литералы в &str
  • stringify! макрос создает &str из всех токенов
ФорматСинтаксисОписание
Display{}Отображение представления
Display{var}Отображение представления для переменной var, реализующей trait Display
Debug{:?}Отладочное представление
Debug{var:?}Отладочное представление для переменной var, реализующей trait Debug
Debug pretty{:#?}Отладочное более читабельное представление
Octal{:o}Восьмеричное представление
LowerHex{:x}Шестнадцатеричное представление в нижнем регистре
UpperHex{:X}Шестнадцатеричное представление в верхнем регистре
Pointer{:p}Адрес указателя в памяти
Binary{:b}Двоичное представление
LowerExp{:e}Экспоненциальное представление в нижнем регистре
UpperExp{:E}Экспоненциальное представление в верхнем регистре
Unicode u32\u{3044}Печать символа Unicode
Position{1} {0}Печать значений в позиции (начиная с 0)
Nameprintln!("{city1} {country}", city1 = "Seoul", country = "Korea");Именованные аргументы

Вывод в Debug коротким синтаксисом:

fn main(){
    let var = ();
    print!("{var:?}");
}

Число Unicode в виде u32, которое вы затем можете использовать для печати символа Unicode \u

fn main(){
    println!("{:X} == \u{3044}", 'い' as u32);// 3044 == い
}
Выравнивание

Выравнивание

^ выравнивание по центру,

< выравнивание слева

> выравнивание справа

fn main(){
  let title = "TODAY'S NEWS";
  // заполнитель - для выравнивания по центру с минимальной длиной 30 символов
  println!("{:-^30}", title);// ---------TODAY'S NEWS---------
  println!("{:-<30}", title);// TODAY'S NEWS------------------
  println!("{title:->30}",);//  ------------------TODAY'S NEWS
}

Формат: { [argument] ':' [[fill] align] [sign] ['#'] [width [$]] ['.' precision [$]] [type] }

ЭлементОписание
argumentНомер (0,1,...) или имя аргумента, например: print("{x}", x = 3)
fillСимвол для заполнения пустых пространств (например, 0), если указана width
alignВыравнивание: влево (<), по центру (^) или вправо (>)
signМожет быть + чтобы знак всегда печатался
#Альтернативное форматирование, например приукрасить Debug форматирование ? или префикс шестнадцатеричный с 0x
widthМинимальная ширина (≥0), заполнение fill (по умолчанию - пробел). Если начинается с 0, дополняется нулями
precisionДесятичные цифры (≥0) для чисел или максимальная ширина для нечисловых
$Интерпретировать width или precision как идентификатор аргумента для динамического форматирования
typeФорматирование: Debug (?), шестнадцатеричный (x), двоичный (b), восьмеричный (o), указатель (p), exp (e)
Пример форматаОбъяснение
{}Вывод следующего аргумента с использованием Display
{:?}Вывод следующего аргумента с использованием Debug
{2:#?}Довольно распечатать 3-й аргумент с форматированием Debug
{val:^25}Выровнять именованный аргумент val по центру с шириной 25
{:<10.3}Выровнять по левому краю с шириной 10 и точностью 3
{val:%#x}Форматировать аргумент val как шестнадцатеричный с ведущим 0x
Полный примерОбъяснение
println!("{x}")Печать x с использованием Display на стандартный вывод с новой строкой
format!("{a:.3} {b:7?}", a = PI, b = 2)Преобразовать PI с 3 цифрами, добавить пробел, b с помощью Debug, вернуть String
fn main() {
// println!("{res_to_debug:?}");
// println!("{res_to_display}");
// {переменная:выравнивание отступов минимум.максимум}

    let title = "TODAY'S NEWS";
    println!("{:-^30}", title);                                   
    let bar = "|";
    println!("{: <15}{: >15}", bar, bar);                         
    let a = "SEOUL";
    let b = "TOKYO";
    println!("{city1:-<15}{city2:->15}", city1 = a, city2 = b);
} 
    ---------TODAY'S NEWS---------
    |                                               |
    SEOUL--------------------TOKYO
Используемые элементыШаблонная строкаРезультат
Минимальная ширина поля{:12} 1234
Минимальная ширина поля{:2}1234
Знак, ширина{:+12} +1234
Начальные нули, ширина{:012}000000001234
Знак, нули, ширина{:+012}+00000001234
Выравнивание влево, ширина{:<12}1234
Выравнивание по центру, ширина{:^12} 1234
Выравнивание вправо, ширина{:>12} 1234
Выравнивание влево, знак, ширина{:<+12}+1234
Выравнивание по центру, знак, ширина{:^+12} +1234
Выравнивание вправо, знак, ширина{:>+12} +1234
Дополнение знаками '=', по центру, ширина{:=^12}====1234====
Двоичная нотация{:b}10011010010
Ширина, восьмеричная нотация{:12o} 2322
Знак, ширина, шестнадцатеричная нотация{:+12x} +4d2
Знак, ширина, шестнадцатеричная с заглавными буквами{:+12X} +4D2
Знак, явный префикс основания, ширина, шестнадцатеричная{:#+12x} +0x4d2
Знак, префикс, нули, ширина, шестнадцатеричная{:#+012x}+0x0000004d2
Знак, префикс, нули, ширина, шестнадцатеричная{:#+06x}+0x4d2
Шестнадцатиричный формат
fn main(){
    format!("{:01$x}", 255, 12); // 0000000000ff
}

01$x по существу означает, что число должно быть напечатано в шестнадцатеричной форме, дополненной нулями до длины, переданной в качестве второго аргумента.

decode_hex

encode_hex

use std::{fmt::Write, num::ParseIntError};
pub fn decode_hex(s: &str) -> Result< Vec< u8 >, ParseIntError> {
    (0..s.len())
        .step_by(2)
        .map(|i| u8::from_str_radix(&s[i..i + 2], 16))
        .collect()
}

pub fn encode_hex(bytes: &[u8]) -> String {
    let mut s = String::with_capacity(bytes.len() * 2);
    for &b in bytes {
        write!(&mut s, "{:02x}", b).unwrap();
    }
    s       
}
fn main(){
 let en = encode_hex(&[1,2,3,4,5,6]);
 println!("{en}");
 let dec = decode_hex(&en);
 println!("{:?}",dec);
}
  • Позиция для каждого аргумента.
fn main(){
    println!("{0}, это {1}. {1}, это {0}", "Алиса", "Боб");
    println!( "{1} {} {nine} {0} {} {nine}", 33, 2, nine=9 );// 2 33 9 33 2 9 
    println!("{1:?}, {0}", "and welcome", Some(42));// Some(42), and welcome
}
  • Именованные аргументы.
fn main(){
    println!("{subject} {verb} {object}",
             object="ленивую собаку",
             subject="быстрая коричневая лиса",
             verb="прыгает через");
}
  • Бинарный
fn main(){  
    println!("{} из {:b} людей знают, что такое двоичный код, а остальные нет.", 1, 2); 
}
  • Число в двоичном формате
fn main(){
 let x = 42; // 42 is '101010' in binary
 println!("{:b}", x);// 101010
 println!("{:#b}", x);// 0b101010
}
  • Вместе RAW и байты
fn main(){ println!("{:?}", br##"I like to write "#"."##);}
  • Вывод байт
fn main(){
    println!("binary -127={:08b}",-127_i8);// -127=10000001
}
  • Вывод f64
fn main(){
    let mut money = 100.99999;
 loop{
  money=money-0.00001f64;
  println!("Money: {0:.5}", money); // Money: 100.99998
  sleep(Duration::new(3, 0));
 }
}
  • Hex, Octal, Binary форматирование
std::fmt::UpperHex
std::fmt::LowerHex
std::fmt::Octal
std::fmt::Binary

Эти черты контролировать представление типа под {:X}, {:x}, {:o} и {:b} спецификаторов формата.

fn main(){
 // число в шестнадцатеричном формате с A до F
 println!("{:#X}", 255);//{:#X} => 0xFF , {:X} => FF

 // RGB (128, 255, 90) 0x80FF5A
 print!( "RGB ({red}, {green}, {blue}) {red:#X}{green:X}{blue:X}",red=128, green=255, blue=90 )
}

Реализуйте эти черты для любого числового типа, с которым вы могли бы подумать о побитовых манипуляциях, таких как | или &

  • Можно выравнивать текст, сдвигая его на указанную ширину. Данный макрос отобразит в консоли
fn main(){
  // "     1". 5 пробелов и "1".
   println!("{number:>width$}", number=1, width=6);

  // Отступ 5 пробелов
   println!("{:<5} {}",1,2);
}
  • Добавить нулей. Данный макрос выведет "000001".
fn main(){
   println!("{number:>0width$}", number=1, width=6);
   println!("{:0width$}|",1, width=5 );//00001|  
   println!("{:04}", 42);    // => "0042
}
  • Задает число десятичных знаков в типах с плавающей запятой {:. *} :
fn main(){
    let formatted_number = format!("{:.*}",2, 1.234567);
    assert_eq!("1.2346",&format!("{:.*}",4, 1.234567));
}

"{:b}" {integer | identifier : [[character]< |^| >][+|-][#][0][width]['.' $|integer|*][identifier | ? | ''] }

fn main(){
   println!("|{:<0width$}| ","g", width=4 );//|g   | из 4 символов выровнен по левому краю
}

fn main(){
    let pi = 3.141592;
    println!("|{name:<4}|", name="a");// |a   |
    println!("|{name:>4}|", name="a");// |   a|
    println!("|{name:^4}|", name="a");// | a  |
}
  • Точность
fn main(){
    println!("|{number:>0width$}|", number=1, width=5);// |00001| добили нулями до 5 символов
    // точность
    let pi = 3.141592;
    println!("{name:1.*}", 3, name=pi);// 3.142 сократили до 3 знаков после запятой
    println!("{name:.3}",  name=pi);// 3.142
    println!("{1:.0$}", 3, pi);// 3.142
    println!("{number:.prec$}",  prec = 3, number = pi);// 3.142
}
  • Выравнивание второй колонки
fn main(){
    for (k,v) in [("key1","fffff"),("keyyy1","gg"),("keyyyyyyy1","hhhhhhhhhhh")]{
        println!("{k:-<width$}{v:?}",  width=16, k=k, v=v );  
    } 
    /*
      key1---------------"fffff"
      keyyy1------------"gg"
      keyyyyyyy1------"hhhhhhhhhhh"
    */
}
  • Выравнивание и ширина
fn main(){
    println!( "Hello {1:0$}!" , 5 , "x" );// Hello x    !
    println!( "Hello {:5}!" ,  "x" );     // Hello x    !
    println!( "Hello {:^1$}!" ,  "x",5 ); // Hello   x  !
    println!( "Hello {:>width$}!","x",width=5 );// Hello     x!
     // 18 длина всей строки
    println!("`{name:>18.*}` выровнять на 3 символа по правому краю", 3, name="1234.56");
}

# экранирует кавычки

## экранирует все

raw-string-literals

fn main(){
 let s = r"foo"; // или let s = "foo";
 let s = r#""foo""#; // или let s = "\"foo\"";
 let s = r##"foo #"# bar"##;// или let s = "foo #\"# bar";

 //  foo::try(); // Error try это ключевое слово языка
 foo::r#try();
}

std::dbg! отладочный макрос

macro.dbg

dbg! печатает в stderr вместо stdout, поэтому журналы отладки легко отделить от фактического вывода stdout нашей программы.

dbg! печатает переданное ему выражение, а также значение, которое оценивается выражением.

dbg! берет на себя ответственность за свои аргументы и возвращает их, чтобы вы могли использовать их в выражениях

fn main(){
    let v = vec![1,2,3];
    dbg!(v);
}
[src/main.rs:39] v = [
    1,
    2,
    3,
]

fn main(){
   let scale = 2;
   let f = dbg!(30 * scale); // stderr output: [src/main.rs:52] 30 * scale = 60
   print!("{}",f);// 60
}

print! , println!

блокируют стандартный вывод при каждом вызове

каждый вызов print! сначала будет блокировать stdout, затем вызывать format!, чтобы разрешить форматирование, и, наконец, сделать системный вызов для фактической печати, все без буферизации данных

Если у вас есть многократные вызовы этих макросов, возможно, лучше заблокировать стандартный вывод вручную.

Например, измените этот код:

fn main(){
    for line in lines {
        println!("{}", line);
    }
}

на этот:

use std::io::Write;
fn main(){
    let mut stdout = std::io::stdout();
    let mut lock = stdout.lock();
    for line in lines {
        writeln!(lock, "{}", line)?;
    }
    // stdout разблокируется при сбросе `lock`
}

eprintln!, ибо ошибки правильнее выводить в STDERR

При cargo run --release вывод STDERR не выводится

fn main(){
  let msg = "Message";
  let data = vec!["item 1","item 2"];
  if cfg!(debug_assertions) { // вариант аттрибута условной компиляции
       eprintln!("debug: {:?} -> {:?}", msg, data);
  }
  // debug: "Message" -> ["item 1", "item 2"]
  eprintln!("Error: arguments --conf can not be empty !");
}

stringify! строит свои аргументы, не нужен Debug

macro.stringify

struct S{x:i32,y:i32};

#[derive(Debug)]
struct S_2{x:i32,y:i32};

fn main(){
 println!("{}",stringify!(S{x:1,y:1})) ;//S { x : 1 , y : 1 }
 println!("{:#?}",S_2{x:1,y:1});  
   /*
   S_2 {
      x: 1,
      y: 1,
   }
   */

 let one_plus_one = stringify!(1 + 1);
 assert_eq!(one_plus_one, "1 + 1");
}

write! макрос вывод в буфер

macro.write

  • Запись в буффер
fn main(){
    use std::fmt::Write;
    let mut s = String::new();
    write!(&mut s, "{} {}", "abc", 123).unwrap();
    assert_eq!("abc 123".to_string(), s);
}
  • Вывод структуры
fn main(){
    struct Structure{x:i32};
    impl std::fmt::Display for Structure {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(f, "({} )", self.x )
        }
    }
    println!(" `{}` ", Structure{x:3});
}
  • write пишет в выходной поток и возвращает fmt::Result
fn main(){
    struct List(Vec<i32>);
    impl std::fmt::Display for List {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
            let vec = &self.0;
            write!(f, "[")?;
            for (count, v) in vec.iter().enumerate() {
                // допишем в f запятую для вывода [0 :1, 1 :2, 2 :3]
                if count != 0 { write!(f, ", ")?; }
                write!(f, "{} :{}", count,v)?;
            }
            // Закроем открытую скобку и вернём значение `fmt::Result`
            write!(f, "]")
        }
    }
  let v = List(vec![1, 2, 3]);
  println!("{}", v);// [0 :1, 1 :2, 2 :3]
}

format!

macro.format

fn main(){
 let s1 = String::from("tic");
 let s2 = String::from("tac");
 let s3 = String::from("toe");

 let s:String = format!("{}-{s2}-{s4}", s1, s4=s3); // tic-tac-toe
}

Struct std::fmt::Formatter

format!

Выделяет память в куче

Методы:

align
alternate
debug_list
debug_map
debug_set
debug_struct
debug_tuple
fill
flags
pad
pad_integral
precision
sign_aware_zero_pad
sign_minus
sign_plus
width
write_fmt
write_str
extern crate core;
use std::fmt::{self, Alignment};

struct Foo;

impl fmt::Display for Foo {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        let s = if let Some(s) = formatter.align() {
            match s {
                Alignment::Left    => "left",
                Alignment::Right   => "right",
                Alignment::Center  => "center",
            }
        } else {
            "into the void"
        };
        write!(formatter, "{}", s)
    }
}
fn main() {
    assert_eq!(&format!("{:<}", Foo), "left");
    assert_eq!(&format!("{:>}", Foo), "right");
    assert_eq!(&format!("{:^}", Foo), "center");
    assert_eq!(&format!("{}", Foo), "into the void");
}

lazy_format!

избегает выделения памяти в куче.

При создании какого-либо объекта, который вы хотите написать или отформатировать, нет причин выделять промежуточные строки (что происходит в format!). Вместо этого lazy_format! захватывает свои аргументы и возвращает непрозрачную структуру с Display реализацией, так что фактическое форматирование может происходить непосредственно в его конечном целевом буфере (таком как файл или строка)

use std::io;
use lazy_format::lazy_format;
use joinery::JoinableIterator;
fn main() {
        let result = (0..10)
                .map(|value| lazy_format!("\t'{}'", value))
                .join_with(",\n")
                .to_string();

        assert_eq!(result,
"        '0'
        '1'
        '2'
        '3'
        '4'
        '5'
        '6'
        '7'
        '8'
        '9'")
}

format_args! - возвращает форматированную строку без выделения памяти в куче Struct core::fmt::Arguments

Этот объект не требует создания кучи в отличие от format!, write!, println! и т.д., и он ссылается только на информацию о стеке. Цель этого макроса состоит в том, чтобы еще больше предотвратить промежуточные выделения при работе со строками форматирования.

use std::fmt;
use std::io::{self, Write};
fn main(){
   let mut some_writer = io::stdout();
   write!(&mut some_writer, "{}", format_args!("print with a {}", "macro"));
}

use std::fmt;
use std::io::{self, Write};
fn main(){
   let  display  =  format ! ( "{:?}" , format_args! ( "{} foo {:?}" , 1 , 2 ));
   let  debug  =  format ! ( "{}" , format_args! ( "{} foo {:?}" , 1 , 2 ));
   println!("{} \n {}",display,debug);
}

fn main(){
    fn my_fmt_fn(args: fmt::Arguments) {
        write!(&mut io::stdout(), "{}", args);
    }
    my_fmt_fn(format_args!(", or a {} too", "function"));
}

 use std::fmt::Write;
 struct A(i32);
 impl std::fmt::Display for A {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "A({})", self.0)
    }
 }
 fn main() {
    let a = A(1);
    let mut buf = String::new();
    buf.write_fmt(format_args!("{}", a)).expect("Display returned an error unexpectedly");
    buf.shrink_to_fit();
    println!("{}",buf);// A(1)
 }

/// Log an error including code location, with `format!`-like arguments.
/// Real code would probably use the `log` crate.
macro_rules! my_log {
    { $($arg:tt)+ } => {
        eprintln!("{}:{}: {}", file!(), line!(), format_args!($($arg)+));
    }
}
fn main(){
    let x = 10u8;
    // Format specifiers:
    // - `x` says print as hex
    // - `#` says prefix with '0x'
    // - `04` says add leading zeroes so width is at least 4
    //   (this includes the '0x' prefix).
    my_log!("x = {:#04x}", x);
}
[dependencies] ansi_term = "0.11"
use ansi_term::Colour::{Black, Red, Green, Yellow, Blue, Purple, Cyan, Fixed};
use ansi_term::Style;
fn main(){
    println!("Demonstrating {} and {}!",
             Blue.bold().paint("blue bold"),
             Yellow.underline().paint("yellow underline"));

    let red_string: String = Red.paint("another red string").to_string();
    println!("{}",red_string);

    let blue_fon: String = Blue.on(Yellow).paint("Blue on yellow!").to_string();
    println!("Синий шрифт, желтый фон {}",blue_fon);

    let color_134: String = Fixed(134).paint("A sort of light purple.").to_string();
    println!("Цвет 134 шрифта из 256  {}",color_134);

    let color_fon: String = Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup.").to_string();
    println!("Цвет № 221 шрифт, № 124 фон {}",color_fon);
}

crate termion (цвет,шрифт)

Интерфейс командной строки

use termion::{color, style};
fn main(){
    let mut image = image::open("examples/image/function/crop.png").unwrap().to_luma();
    let v = image.into_vec();
    for (index, value) in v.iter().enumerate() {
        if index % 60 == 0 { println!(""); }
        if value < &50 {
            print!("{}{number:>width$}{}", color::Fg(color::Green), style::Bold, number = value, width = 4);
        } else if value < &90 {
            print!("{}{number:>width$}{}", color::Fg(color::Blue), style::Bold, number = value, width = 4);
        } else {
            print!("{}{number:>width$}{}", color::Fg(color::LightRed), style::Reset, number = value, width = 4);
        }
    }
}

std::concat! макрос объединяет литералы в &str

std::stringify! макрос создает &str из всех токенов

fn main(){
    let s:&str = concat!("test", 10, 'b', true,44.88);
    assert_eq!(s, "test10btrue44.88");

    let one_plus_one = stringify!(1 + 1);
    assert_eq!(one_plus_one, "1 + 1");
}

Типаж Debug

std::fmt::Debug

std::fmt::Display

Binary

std::string::ToString

для всех Display автоматически реализуется ToString

Display не может быть получен автоматически derive

std::fmt::Debug {:?}

std::fmt::Display {}

std::fmt::Binary {:b}

std::string::ToString

use std::fmt;
// #[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl fmt::Debug for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Point {{ x: {}, y: {} }}", self.x, self.y)
    }
}
fn main(){  
 let origin = Point { x: 0, y: 0 };
 println!("The origin is: {:?}", origin);
 println!("The origin is: {:#?}", origin);
} 

Если использовать вывод {} нужна реализация Display

impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{} {}", self.x, self.y)
        }
}
fn main(){ println!("The origin is: {}", origin);} 

Если использовать бинарный вывод {:b} то нужна реализация std::fmt::Binary

struct Point2D {
    x: f64,
    y: f64,
}
impl std::fmt::Binary for Point2D {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "x:{:b},y:{:b}", self.x as i32,self.y  as i32) // delegate to i32's implementation
    }
}
fn main(){ 
 let origin = Point { x: 0, y: 0 };
 println!("The origin is: {:b}", origin);
}

use std::string::ToString;
struct Circle {
    radius: i32
}
impl ToString for Circle {
    fn to_string(&self) -> String {
        format!("Circle of radius {:?}", self.radius)
    }
}
fn main(){ 
 let circle = Circle { radius: 6 };
 println!("{}", circle.to_string());
}

if является выражением

fn main(){
    let x = 5;
    let i = if x == 5 {55} else if x == 6 {66} else { 0 };
    assert_eq!(i,55);

    if x == 5 {
    } else if x == 6 {
    } else {
    }
}

if без else всегда возвращает () в качестве значения Его значением является значение последнего выражения из выбранной ветви

тернарный оператор

fn main(){
   let lat_c = if 2 >= 1 { 'N' } else { 'S' };
   let res = if x == 2{Some(x)}else{None};

   let co = (1 > 3).then_some(5).unwrap_or(6);
}

возвращаемое значение из if в let должно быть одного типа, даже в кортеже последовательность типов


fn main(){
 let result = if 1 < 0 { 
   (1,"hi",true) 
 }else { 
   (2,"hi",true)
 };
}

скомбинировать if и let чтобы более удобно сделать сопоставление с образцом

сравнения любого значения перечисления

Надо реализовать для типа == ``` // if //if E::type2(String) == option.name { // println!("t") Надо реализовать для типа == //} ```
fn main(){
   enum E{ type1(String), type2(String), type3(String) }

    struct Type<E>{ name:E }

    let option:Type<E> =  Type {name:E::type2(" my message ".to_string())};
    
    // match
    match option.name {
        E::type1(s) => {println!("type1: {}",s)},
        E::type2(s)  => {println!("type2: {}",s)},
        E::type3(s)  => {println!("type3: {}",s)},
        _ => println!("default")
    }
    
    // if let
    if let E::type1(value) = option.name  {
        println!("type1: {}",value)
    }else if let E::type2(value) = option.name  {
        println!("type2: {}",value)
    }else if let E::type3(value) = option.name  {
        println!("type3: {}",value)
    }

  // Для простого if
  // требуется реализация #[derive(PartialEq)] для enum E
   if E::type2(" my message ".to_string()) == option.name  {
        println!("type2: {}"," my message ")
    }

  if let res = exp { one } else{ two };
}

Минимизация прыжков т.е. if, while, for, match.

Писать логику так, чтобы процессор не захлебывался

Прыжки (условные переходы) тормозят конвейер (pipeline) вычисления процессора. Современный процессор загружает в конвейер десятки инструкций наперед, а jmp ломает эту цепочку.

  • Условные передачи (cmov): Современные компиляторы иногда заменяют if на специальные инструкции, которые вообще не требуют прыжков, что делает код невероятно быстрым. Например замена if на тернарный оператор
  • Предсказание переходов (Branch Prediction): Процессор пытается угадать, прыгнет он или нет, еще до того, как выполнит cmp. Если он ошибается, он теряет кучу тактов на очистку конвейера.

Вот основные стратегии, как писать код, чтобы процессор «угадывал» чаще:

1. Сортировка данных

Если мы обрабатываем массив в цикле и внутри есть if, процессор будет угадывать почти идеально, если данные отсортированы.

2. Избавление от веток (Branchless Programming)

Если можно заменить if математикой — сделайте это.

Пример: Найти сумму всех элементов массива, которые больше 10.

С ветвлением:
if (a[i] > 10) sum += a[i];

Без ветвления (математически):
sum += (a[i] > 10) * a[i];

итог: у процессора него нет выбора пути, а значит, нет риска ошибки. Нет прыжка — нет риска ошибиться в предсказании — код работает быстрее.

3. Использование тернарного оператора (cmov)

Компиляторы часто превращают простые тернарные операторы в инструкцию cmov (Conditional Move)

int max = (a > b) ? a : b;
итог: Нет прыжка — нет риска ошибиться в предсказании — код работает быстрее.

4. Избегайте switch с кучей мелких кейсов

Если в switch 20 разных вариантов, которые выпадают случайно, предсказатель переходов просто «сойдет с ума». В таких случаях лучше либо структурировать данные иначе, либо использовать таблицы переходов (которые компилятор сам создаст, если кейсы идут плотно по порядку).

5. Пишите простые условия выхода из цикла.

Чем проще условие в while, тем легче компилятору превратить его в эффективный do-while с одним прыжком.

6. Вероятные и невероятные ветки (Likely/Unlikely)

Если ты точно знаешь, что одно условие срабатывает в 99% случаев (например, проверка на ошибку, которой почти никогда не бывает), ты можешь подсказать компилятору.

#![allow(unused)]
fn main() {
use std::intrinsics::{likely, unlikely};

// помогаем Branch Predictor (предсказателю переходов) внутри процессора более вероятный путь для просчета на перед
if unlikely(ptr.is_null()) {
    // Код обработки редкой ошибки
} else {
    // Основной код
}
}

Однако, в стабильном Rust эти функции все еще часто скрыты за внутренними механизмами. Поэтому самый надежный и переносимый способ сегодня — это использование крейтов (библиотек), таких как likely

Атрибут #[cold] говорит компилятору: «Эта функция вызывается редко, выкинь её код подальше от основного цикла, чтобы она не засоряла кэш инструкций».

#![allow(unused)]
fn main() {
#[cold]
fn handle_rare_error() {
    // Очень сложный и редкий код
}

if ptr.is_null() {
    handle_rare_error();
}
}

Refutability

if let else if let

fn main() {
    let (x, y) = (1, 2);               // "(x, y)" is an irrefutable pattern
    
    if let (a, 3) = (1, 2) {           // "(a, 3)" is refutable, and will not match
        panic!("Shouldn't reach here");
    } else if let (a, 4) = (3, 4) {    // "(a, 4)" is refutable, and will match
        println!("Matched ({}, 4)", a);
    }
}

if let variant | variant

enum Creature {
    Crab(String),
    Lobster(String),
    Person(String),
}
fn main() {
    let state = Creature::Crab("Ferris");

    if let Creature::Crab(name) | Creature::Person(name) = state {
        println!("This creature's name is: {}", name);
    }
}

деструктуризация массива

fn main(){
   let list = vec![1,2,3];
   if let [one, ..] = list.as_slice() {
     assert_eq!(1,*one);
   }
}

деструктуризация структуры

patterns

fn main() {
    struct Car;
    struct Computer;
    struct Person {
        name: String,
        car: Option<Car>,
        computer: Option<Computer>,
        age: u8,
    }
    let person = Person {
        name: String::from("John"),
        car: Some(Car),
        computer: None,
        age: 15,
    };
    if let
        Person {
            car: Some(_),
            age: person_age @ 13..=19,
            name: ref person_name,
            ..
        } = person
    {
        println!("{} has a car and is {} years old.", person_name, person_age);
    }
}

улучшает if let

fn main(){
    // Создадим переменную `optional` с типом `Option<i32>`
    let mut optional = Some(0);

    // Это можно прочитать так: "Пока `let` деструктурирует `optional` в  `Some(i)`, выполняем блок (`{}`). В противном случае `break`.
    while let Some(i) = optional {
        if i > 9 {
            println!("Больше 9, уходим отсюда!");
            optional = None;
        } else {
            println!("`i` равен `{:?}`. Попробуем ещё раз.", i);
            optional = Some(i + 1);
        }
    }
    //  К `if let` можно добавить дополнительный блок `else`/`else if` но к `while let`  нельзя.
}

while match func...

fn gen(index:usize)->Option<i32>{
     let data:Vec<Option<i32>> = vec![Some(1),Some(2),Some(3),Some(4),None,Some(6)];  
     if index >= data.len(){
         None
     }else{
        data[index]  
     }
} 
fn main() {    
  let mut current_index = 0;  
  while match gen(current_index) {
     Some(i) => {
       println!("{:?}",i);
       current_index+=1;
       true
     },
     None => {false}
  }{   
      println!("current_index={}",current_index);
  }  
/*
    1
    current_index=1
    2
    current_index=2
    3
    current_index=3
    4
    current_index=4
*/ 
}

loop - Бесконечный

while - Условие

for - Повторения блока кода определённое количество раз

В Rust циклы являются выражениями, но не порождают полезных значений. Значением цикла всегда является ()

Устранение зависимости по данным (Data Dependency)

Современные процессоры суперскалярны (Superscalar execution) они имеют несколько конвейеров (pipeline) работающих параллельно, которые загружены теми инструкциями кода который предсказал (Branch Predictor), когда предсказатель ошибается (Branch Misprediction), процессор выкидывает все результаты из всех параллельных линий и полностью очищает конвейер. Если есть работа которая не зависит от предыдущей работы то это будет просчитано в одном из конвейеров заранее. Например независимые данные будут просчитаны параллельно

a = b + c;
d = e + f;

В таком случае конвейер будет стоять и ждать, пока первый закончит работу. Это называется Data Dependency (зависимость по данным):

a = b + c;
d = a + f; // d зависит от результата первой строки

Например, развертывание циклов (Loop Unrolling).

1. Обычный цикл (Зависимый)

Здесь на каждой итерации переменная sum должна быть обновлена, прежде чем начнется следующая итерация. Это создает «бутылочное горлышко» в конвейере.

#![allow(unused)]
fn main() {
fn sum_simple(a: &[i32]) -> i32 {
    let mut sum = 0;
    for x in a {
        sum += x; // Конвейер ждет предыдущий результат sum
    }
    sum
}

}

2. Развернутый цикл (Loop Unrolling)

В этом варианте мы создаем две независимые переменные. Процессор видит, что sum1 и sum2 никак не связаны между собой, и задействует два разных порта исполнения одновременно.

#![allow(unused)]
fn main() {
fn sum_unrolled(a: &[i32]) -> i32 {
    let mut sum1 = 0;
    let mut sum2 = 0;

    // Идем "шагами" по 2 элемента
    // chunks_exact позволяет итерироваться по слайсам фиксированного размера
    let chunks = a.chunks_exact(2);
    let remainder = chunks.remainder(); // Остаток, если длина массива нечетная

    for chunk in chunks {
        sum1 += chunk[0]; // Выполняется в Конвейере 1
        sum2 += chunk[1]; // Выполняется в Конвейере 2 параллельно
    }

    // В конце складываем результаты двух "потоков" и остаток
    sum1 + sum2 + remainder.iter().sum::<i32>()
}

}

Это будет работать почти в два раза быстрее, потому что мы дали процессору возможность загрузить две параллельные линии одновременно. Мы убрали «зависимость по данным» (Data Dependency) на уровне одной итерации.


А теперь магия: Авто-векторизация (SIMD)

Если написать код правильно, современный компилятор Rust сделает не 2 шага, а 8 или 16 (используя 256-битные регистры AVX). Он превратит цикл в один мощный удар по памяти.

Чтобы помочь Rust это сделать, часто достаточно просто использовать итераторы:

#![allow(unused)]
fn main() {
// Часто этот вариант будет самым быстрым, 
// так как LLVM превратит его в развернутый SIMD цикл автоматически
fn sum_iterator(a: &[i32]) -> i32 {
    a.iter().sum()
}

}

Для чего нужен loop

Компилятор должен проверить поток выполнения программы (flow-sensitive) на допустимость выполнения условий. Цикл while содержит какие-то требования exp, следовательно, при не соблюдении этих требований, поток выполнения пойдет иным образом но в коде не предусмотренно для этого варианта, тут приходит на помощь loop

fn test()->i32{
/*
    while exp {
        return 1i32;
    }
*/
    
    loop{
        return 1i32;
    }
}
fn main(){}

Прерывая loop, вы можете также вернуть из него значение.

Логика if (break {....}){} оценивает значение внутри скобок, а логика if break {....}{} нет т.е. сразу возвращает

fn main(){
 let v = loop {
     break "found the 13";
 };
}

fn break1() {
    loop {
        if (break { print!("1") }) {... }
    }
}

Условие оператора if — это выражение break со значением, которое выходит из окружающего цикла со значением { print!('1') } типа (). Подобно return 1, чтобы разорвать это значение, необходимо оценить значение, и эта функция печатает 1. break1(); // 1 т.е. выход из цикла со значением { print!("1") }


fn break2() {
    loop {
        if break { print!("2") } {
        }
    }
}

break2() вывода не будет, тут логика break без возврата значения Здесь мы наблюдаем разницу между грамматикой break и грамматикой return. В отличие от return, ключевое слово break в условии этого оператора if не анализирует значение, начинающееся с фигурной скобки.

Этот код анализируется как:

fn main(){
    loop {
        if break {
            print!("2")
        }
        {}
    }
}

loop

fn main(){
    let mut i = 0;
    loop {
        i=i+1;
        if i == 3 {continue;}
        println!("Зациклились! {}",i );
        if i >= 10 {break};
    }

    // возврат значения из loop после break
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;
        }
    };
}

loop

fn main(){
    let x: ! = loop {};
// ненаселённый тип
// может выступать в роли любого другого типа
    let x: u32 = loop {};
// пока ещё не настоящий тип
}

loop

fn main(){
     let uninit;
     while true {
        if condition {
            uninit = 92;
            break;
        }
     }
     pritnln!("{}", uninit);// error не факт что будет итерация в цикле
}

fn main(){
     let init;
     loop {
        if condition {
            init = 92;
            break;
        }
     }
     pritnln!("{}", init); // ok
}

loop с меткой возврата

'label: loop {} Метка цикла, полезна для управления потоком во вложенных циклах.

break x То же самое, но сделать x значение выражения цикла (только в реальном цикле).

break 'label Выйти не только из этого цикла, но и из цикла, помеченного 'label.

break 'label x То же самое, но сделайте x значением охватывающего цикла, отмеченным знаком 'label.

continue Продолжить выражение REF до следующей итерации цикла этого цикла.

continue 'label То же самое, но вместо этого цикла заключительный цикл отмечен 'label.

fn main(){
    let mut i:i32=0;
    let mut y:i32=4;

    'outer: loop {
        i=0;
        y-=1;
        println!("----------------- y:{}",y);
        if y==0{
            break;
        }
        loop{
            println!("i:{}",i);
            i+=1;
            if i>10{
                continue 'outer;
            }
        }
    }
     
}

fn main(){
 'outer: for i in 0..4 {
    for j in i..i+2 {
        println!("{} {}", i, j);
        if i > 1 {
            continue 'outer;
        }
    }
    println!("--");
 }
}

while

fn main(){
 let mut i = 0;
 while i < 10 {
    i=i+1;
    println!("Зациклились! {}",i );
    if i >= 10 {break};
 }
}

ref Заимствование значения

for &(i, ref line) in local.iter() {

}

for

Инструкция for in неявно вызывает для коллекции реализацию into_iter которая потребляет коллекцию и она после недоступна т.е. после for in коллекции нет

fn main(){
    let v = vec![1,2,3];
    for i in v{} => for i in v.into_iter(){}
}

0..10 это выражение возвращает итератор, до 10 (не включая), аналогично std::ops::Range { start: 0, end: 10 } и только диапазон с начальным значением может итерироваться ("..end" не итерируется )

fn main(){
    for x in 0..10 {
        print!("{}", x); // x: i32
    }
}

1..=5 диапазон с включая последнее значение:

fn main(){
    for n in 1..=5  {
        println ! ("{}", n);
    }
}

fn main(){
 // let array:[i32;10]=[1,2,3,4,5,6,7,8,9,10];
 let mut array:[i32;10]= [2;10];

 for x in &array {
    print!("{} ", x);
 }
}

fn main(){
    for n in 0.. {} // бесконечный цикл
}

fn main(){
 for city in [
    City { name: "Дублин", lat: 53.347778, lon: -6.259722 },
    City { name: "Осло", lat: 59.95, lon: 10.75 },
    City { name: "Ванкувер", lat: 49.25, lon: -123.1 },
 ].iter() {
    println!("{}", *city);
 }
}

for метки циклов

Когда у вас много вложенных циклов, вы можете захотеть указать, к какому именно циклу относится break или continue

fn main(){
 'outer: for x in 0..10 {
    'inner: for y in 0..10 {
        if x % 2 == 0 { continue 'outer; } // продолжает цикл по x
        if y % 2 == 0 { continue 'inner; } // продолжает цикл по y
        println!("x: {}, y: {}", x, y);
    }
 }
}

for как устроен внутри

IntoIterator

fn main(){
 let values = vec![1, 2, 3, 4, 5];
 for x in values {
    println!("{x}");
 }
}

Rust расщепляет этот values на:

fn main(){
 let values = vec![1, 2, 3, 4, 5];
 {
    let result = match IntoIterator::into_iter(values) {
        mut iter => loop {
            let next;
            match iter.next() {
                Some(val) => next = val,
                None => break,
            };
            let x = next;
            let () = { println!("{x}"); };
        },
    };
    result
 }
}

while let

fn main(){
 loop {
    match option {
        Some(x) => println!("{}", x),
        _ => break,
    }
 }
}

loop превращается в такой while:

fn main(){
 while let Some(x) = option {
    println!("{}", x);
 }
}

ranges

Пo lo..hi и lo.. можно итерироваться

fn main(){
 let bounded = 0..10;
 let from = 0..;
 let to = ..10;
 let full = ..;
 for i in (0..10).step_by(2) {
    println!("i = {}", i);
 }
}

Рекурсия

Рекурсию применяют при работе:

  • с вложенными структурами - дерево, граф
  • каталог файловой системы
  • парсинг json
  • алгоритм поиска пути

Глубокая рекурсия может привести к stack overflow, так как каждый вызов функции это прыжок для процессора, что вынуждает заполнять Stack вызовов кадром стека Stack Frame из контекста.

В каждый Stack Frame входит:

  • Адрес возврата (Return Address): Самое важное — адрес той инструкции, которая идет сразу за вызовом функции.
  • Аргументы функции: Все переменные, которые ты передал (если они не влезли в регистры).
  • Локальные переменные: Всё, что ты объявил внутри функции (например, let x = 10;).
  • Сохраненные регистры: Если функция собирается менять регистры, которые важны для «родительской» функции, она обязана их забэкапить в стек.

В этой рекурсии происходит накопление Stack Frame, потому что результат рекурсивного вызова нужен после возврата. Т.е. каждый рекурсивный вызов функции ожидает после возврат для продолжения вычисления. Есть работа после вызова и следовательно нужно использовать данные из stack frame!

fn factorial(n: u64) -> u64 {
    if n == 0 {
        1
    } else {
        n * factorial(n - 1)
    }
}
fn main() {
    let n = 5;
    println!("{}! = {}", n, factorial(n));
}

Выполнение выглядит так:

  • для состояния factorial(5) ждёт результат factorial(4) что бы умножить на n, т.е. нельзя вернуть результат сразу, пока он не вычислен→ stack frame остаётся в стеке вызовов
  • для состояния factorial(4) ждёт результат factorial(3)→ еще один stack frame в стек вызовов
  • ...
  • для состояния factorial(0) ничего не ждет и возвращает результат 1, stack frame не создается

Хвостовая рекурсия (tail recursion) — это когда рекурсивный вызов является последней операцией функции.

Разница от обычной рекурсии в том, что именно сохраняется в stack frame и зачем.

В хвостовой рекурсии тоже происходит накопление stack frame, но не потому что данные нужны для расчета после возврата, stack frame сохраняется просто из-за соглашения ABI языка, по сути stack frame лишний, так как все необходимые данные вычисляются до рекурсивного вызова и все состояние передается в аргументах функции.

Нет работы после вызова и, следовательно, не нужно использовать данные из stack frame после!

fn factorial_tail(n: u64, acc: u64) -> u64 {
    if n == 0 {
        acc
    } else {
        factorial_tail(n - 1, acc * n)
    }
}
fn main() {
    let n = 5;
    println!("{}! = {}", n, factorial_tail(n, 1));
}

Выполнение выглядит так (каждый шаг полностью завершён):

  • fact_tail(5, 1)
  • → fact_tail(4, 5)
  • → fact_tail(3, 20)
  • → fact_tail(2, 60)
  • → fact_tail(1, 120)
  • → fact_tail(0, 120)

Tail Call Optimization (TCO) (в Haskell, Scala но Rust не гарантирует TCO) - это оптимизация управления потоком.

Хвостовая рекурсия дает почву для оптимизации, если функция не собирается возвращаться в текущий frame, то незачем и сохранять return address и стек.

Оптимизация заключается в том, что tail call заменяется на переход (jmp) вместо call + ret.

В результате компилятор может выполнить код с постоянной глубиной стека: новые stack frame не создаются, а всё состояние передаётся через аргументы.

TCO позволяет использовать рекурсию без stack overflow.

Почему в Rust нет TCO:

TCO требует уничтожить stack frame раньше, чем Rust разрешает вызывать Drop.

  • есть деструктор Drop
  • есть RAII
  • есть гарантированная очередность освобождения

Иллюстрация особенности рекурсии

Не нужно явно хранить стек вызовов

Quick Sort в двух вариантах: с рекурсией и с явным стеком (итеративно). Пример с рекурсией как раз хорошо показывает, где она удобна: процессор автоматически использует стек вызовов, и нам не нужно вручную хранить «границы подмассивов».

Рекурсивная реализация Quick Sort

fn quick_sort_recursive(arr: &mut [i32]) {
    if arr.len() <= 1 {
        return;
    }

    let pivot_index = partition(arr);
    let (left, right) = arr.split_at_mut(pivot_index);
    quick_sort_recursive(left);
    quick_sort_recursive(&mut right[1..]); // исключаем pivot
}

fn partition(arr: &mut [i32]) -> usize {
    let pivot = arr[arr.len() - 1];
    let mut i = 0;
    for j in 0..arr.len() - 1 {
        if arr[j] <= pivot {
            arr.swap(i, j);
            i += 1;
        }
    }
    arr.swap(i, arr.len() - 1);
    i
}

fn main() {
    let mut data = [33, 2, 52, 106, 73, 10];
    quick_sort_recursive(&mut data);
    println!("{:?}", data);
}

Итеративная реализация с явным стеком

(Но стек находится в памяти, не в call stack, что позволяет контролировать глубину и избегать переполнения стека.)

fn quick_sort_iterative(arr: &mut [i32]) {
    let mut stack = Vec::new();
    stack.push((0, arr.len() - 1));

    while let Some((low, high)) = stack.pop() {
        if low >= high {
            continue;
        }

        let pivot_index = partition_indices(arr, low, high);

        if pivot_index > 0 {
            stack.push((low, pivot_index - 1));
        }
        stack.push((pivot_index + 1, high));
    }
}

fn partition_indices(arr: &mut [i32], low: usize, high: usize) -> usize {
    let pivot = arr[high];
    let mut i = low;
    for j in low..high {
        if arr[j] <= pivot {
            arr.swap(i, j);
            i += 1;
        }
    }
    arr.swap(i, high);
    i
}

fn main() {
    let mut data = [33, 2, 52, 106, 73, 10];
    quick_sort_iterative(&mut data);
    println!("{:?}", data);
}

Деструкция объекта и сопоставление с образцом

Для замены группы операторов if/else, принимает один тип данных

Аналог макрос matches!

macro.matches

using-matches

macro_rules! matches {
    ($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )? $(,)?) => {
        match $expression {
            $( $pattern )|+ $( if $guard )? => true,
            _ => false
        }
    }
}

pub enum Test { FIRST, SECOND }

fn is_first(data: Test) -> bool {
    matches!(data, Test::FIRST)
}

fn is_first_2(data: Test) -> bool {
     match data {
            Test::FIRST => true,
            _ => false,
    }
}
fn main() {
   println!("{}", is_first(Test::FIRST));
   println!("{}", is_first_2(Test::FIRST));
}

Последовательный фильтр значений

fn main(){
 let rgb = (200, 0, 0);

 match rgb {
    (r, _, _) if r < 10 => println!("Not much red"),
    (_, g, _) if g < 10 => println!("Not much green"),
    (_, _, b) if b < 10 => println!("Not much blue"),
    _ => println!("Each color has at least 10"),
 }
}

Результат в переменную

fn main(){
 let x:i32 = 5;
 let y:i32 =  match x {
          5 => 5*5,
          _ => 0+0,
 };
 print!("{}",y);// 25
}

match Error

Err(x @ Error {..}) => {}

Получить оригинал без декомпозиции

match Err::<&str,_>("Error"){
     Ok(_) => {},
     res => { print!("{:?}",res);}
}
fn main(){
 let x = 3;
 match x {
        1 => println!("один"),
        2 => println!("два"),
        3 | _ => println!("три или больше"),
        0 ..= 128 => println!("от 0 до 128 включая"),
        129 ..= 255 => println!("от 129 до 255 включая"),
        .. =3 => println!("от 0 до 3 включая"),
       origin @ (0..=3)   => println!("от 0 до 3 включая, origin={}",origin),
        n => println!("любое {}",n),
        _ => println!("что-то ещё"),// default если нет совпадения
 }
}

чек для палиндромов

slice-patterns

fn is_palindrome(items: &[char]) -> bool {
    match items {
        [first, middle @ .., last] => first == last && is_palindrome(middle),
        [] | [_] => true,
    }
}
fn main(){
 assert_eq!(is_palindrome(&['r', 'a', 'c', 'e', 'c', 'a', 'r']), true);
 assert_eq!(is_palindrome(&['h', 'e', 'l', 'l', 'o']), false);
}

slice match

fn main(){
 let words = vec!["a", "b", "c"];
 let slice = &words[..];
 match slice {
    [] => println!("slice is empty"),
    [one] => println!("single element {}", one),
    [head, tail @ ..] => println!("head={} tail={:?}", head, tail),
 }
}

match slice {
    // Игнорируйте все, кроме последнего элемента, который должен быть "!".
    [.., "!"] => println!("!!!"),

    // `start` - это кусочек всего, кроме последнего элемента, который должен быть « z ».
    [start @ .., "z"] => println!("starts with: {:?}", start),

    // `end` - это фрагмент всего, кроме первого элемента, который должен быть « a ».
    ["a", end @ ..] => println!("ends with: {:?}", end),

    origin => println!("{:?}", origin),
}

if let [.., penultimate, _] = slice {
    println!("next to last is {}", penultimate);
}

fn main(){
 let tuple = (1, 2, 3, 4, 5);
 // Шаблоны tuple также могут использоваться в шаблонах структур кортежей и кортежей.
 match tuple {
    (1, .., y, z) => println!("y={} z={}", y, z),
    (.., 5) => println!("tail must be 5"),
    (..) => println!("matches everything else"),
 }
}

Соответствие началу фрагмента

slice-patterns

use std::error::Error;

fn is_elf(binary: &[u8]) -> bool {
    match binary {
        [0x7f, b'E', b'L', b'F', ..] => true,
        _ => false,
    }
}
fn main() -> Result<(), Box<dyn Error>> {
    let current_exe = std::env::current_exe()?;
    let binary = std::fs::read(&current_exe)?;

    if is_elf(&binary) {
        print!("{} is an ELF binary", current_exe.display());
    } else {
        print!("{} is NOT an ELF binary", current_exe.display());
    }

    Ok(())
}

формула деструкции

match c {
//   Patterns           MatchArmGuard
// ↓↓↓↓↓↓↓↓↓         ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 
  ) | ] } if stack.pop() != Some(c) => return false,
  _ => {}
}
----------------------------------
[1, ..] => {}
[1, .., 5] => {}
[1, x @ .., 5] => {}
[a, x @ .., b] => {}
1 .. 3 => {}
1 ..= 3 => {}
1 .. => {}
x @ 1..=5 => {}
S { x } if x > 10 => {}
let (_, b, _) = abc;
let (a, ..) = abc;
let (.., a, b) = (1, 2);
fn f(S { x }: S)

Деструктуризация кортежей ()

fn main(){
  let pair = (10, -2); 

 // Match можно использовать для деструктуризации кортежей
  match pair {
        // Деструктурируем два значения
        (0, y) => println!("Первое значение `0`, а `y` равно `{:?}`", y),
        (x, 0) => println!("`x` равно `{:?}`, а второе значение `0`", x),
        _     => println!("Неважно, какого они значения"),
        // `_` означает, что значение не будет связано с переменной
 }
}

Деструктуризация струтктуры

struct Struct {
   a: i32,
   b: char,
   c: bool,
}
fn main(){
 let mut struct_value = Struct{a: 10, b: 'X', c: false};

 match struct_value {
    Struct{a: 10, b: 'X', c: false} => (),
    Struct{a: 10, b: 'X', ref c} => (),
    Struct{a: 10, b: 'X', ref mut c} => (),
    Struct{a: 10, b: 'X', c: _} => (),
    Struct{a: _, b: _, c: _} => (),
 }
}

struct Point{
    x:i32,
    y:i32
}
fn main(){
    let point:Point = Point{x:5,y:6};
    match point {
        Point{x:x_,..} => println!("{}",x_)
    }
}

Вы можете связать значение с именем с помощью символа @

#[derive(Debug)]
struct Person {
    name: Option<String>,
}
fn main(){
    let name = "Steve".to_string();
    let mut x: Option<Person> = Some(Person { name: Some(name) });
    match x {
        Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a),
        _ => {}
    }
}

Деструктуризация enum

enum Message{
        Hello{id:i32}
}
fn main(){
   let msg = Message::Hello {id:5};
   match msg { 
        Message::Hello {id: id_variable @ 3...7}=>{},
        Message::Hello {id:10...12}=>{},
        Message::Hello {id}=>{}
   }
}

Шаблоны сопоставления match

1 | 2 // ИЛИ
Point {_, y, z } => println!("({},{})", y, z)// деструктуризация структуры с пропуском
MyEnum::ChangeColor(_,_,_) => println!("игнорируем"),// Игнорирование связывания перечисления

1 ... 5 => println!("от одного до пяти")  //Сопоставление с диапазоном
'а' ... 'и' => println!("ранняя буква"),
e @ 1 ... 5 | e @ 8 ... 10 => println!("получили элемент диапазона {}", e),// связать значение с именем

все ветки должны иметь один тип (но помним про ! ) match может поглощать аргумент

Нужно явно указывать все ветки:

  • _ матчит всё, что угодно
  • .. игнорирует остальные поля
fn main(){
 enum E {
   One(usize),
   Two { value: Vec<u32>, other: Vec<u32> },
   Tree,
 }

fn foo(e: E) {
 match e {
    E::One(x) => x,
    E::Two { value: xs, .. } => xs.len(),
    _ => 92,
  }
 }
}

берем ссылку на вектор

Если матчить &T, а не T, то результат — & ссылки исходное значение не поглощается, аналогично для &mut T

fn foo(r: Result<Vec<u32>, Error>) {
   let xs: &Vec<u32> = match &r { // берем ссылку на вектор
        Ok(xs) => xs,
        Err(_) => return,
    };// доступен вектор r для дальнейшего использования
}
fn main(){}

Ограничители шаблонов

1

4 | 5 if y => println!("да") // y глобален
OptionalInt::Value(i) if i > 5 => println!("Получили целое больше пяти!"),

fn main() {
    let pair = (2, -2);

    println!("Расскажи мне о {:?}", pair);
    match pair {
        (x, y) if is_zero(x) => println!("Zero"),
        (x, y) if x == y => println!("Близнецы"),
        // Данное ^ `условие if` является ограничителем шаблонов
        (x, y) if x + y == 0 => println!("Антиматерия, бабах!"),
        (x, _) if x % 2 == 1 => println!("Первое число нечётно"),
        _ => println!("Нет корреляции..."),
    }
}

match events {
  v if v as i32 & libc::EPOLLIN == libc::EPOLLIN => {
      context.read_cb(key, epoll_fd)?;
  }
  v if v as i32 & libc::EPOLLOUT == libc::EPOLLOUT => {
      context.write_cb(key, epoll_fd)?;
      to_delete = Some(key);
  }
  v => println!("unexpected events: {}", v),
};

Сопоставление enum

enum Item {
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

fn switch(msg:Item){
    match msg {
        Item::ChangeColor(r, g, b) => println!("change_color RGB:{}{}{}",r,g,b),
        Item::Move { x, y} =>  println!("move_cursor x:{} y:{}",x,y),
        Item::Write(s) => println!("{}", s),
        // _ =>  println!("default")
    }
 
   match msg {
        Item::ChangeColor(r, ..) => println!("change_color RGB:{}{}{}",r,g,b),
        Item::Move { .. } =>  println!("move_cursor x:{} y:{}",x,y),
        Item::Write(s) => println!("{}", s),
        // _ =>  println!("default")
  }
}
fn main(){
   let msg_move: Item = Item::Move { x: 3, y: 4 };
   let msg_write: Item  = Item::Write("Hello, world".to_string());
   let msg_color: Item  = Item::ChangeColor(44,1,44);
   switch(msg_move);
   switch(msg_write);
   switch(msg_color);
}

ref и ref mut в шаблоне match (УСТАРЕВШИЙ)

legacy-patterns-ref-and-ref-mut

the-ref-keyword-in-rust

Если вы хотите получить ссылку в шаблоне match, то используйте ключевое слово ref

fn main(){
    let mut x = 5;
    match x {
        1 | 2 => println!("один или два"),
        3 => println!("три"),
        ref mut mr =>   foo(mr) ,// тип &mut i32
    }

    println!("global {}",x);// 9

    fn foo(l:&mut i32){
        *l=9;
        println!("foo {}",l);// 9
    }
}
 fn first_name(&self) -> Option<&str> {
        match self.first_name {
            Some(ref v) => Some(v.as_str()),
            None => None,
        }
    }

// Rust 2018, напротив, будет выводить &s и refs, а ваш оригинальный код будет просто работать.

 fn first_name(&self) -> Option<&str> {
        match &self.first_name {
            Some(v) => Some(v.as_str()),
            None => None,
        }
}
fn main(){}

Деструктуризация ссылки &, ref и ref mut

fn main(){
// Присваиваем ссылку на тип `i32`.
// Символ `&` означает, что присваивается ссылка.
    let reference = &4;

    match reference {
        // Если `reference` - это шаблон, который сопоставляется с `&val`,то это приведёт к сравнению: `&i32`
        &val => println!("Получаем значение через деструктуризацию: {:?}", val),
    }

// Чтобы избежать символа `&`, нужно разыменовывать ссылку до сопоставления.
    match *reference {
        val => println!("Получаем значение через разыменование: {:?}", val),
    }

    // Что если у нас нет ссылки? 
    let _not_a_reference = 3;

// Rust предоставляет ключевое слово `ref` именно для этой цели.
// Оно изменяет присваивание так, что создаётся ссылка для элемента.

    // Соответственно, для определения двух значений без ссылок, ссылки можно назначить с помощью `ref` и `ref mut`.
    let value = 5;
    let mut mut_value = 6;

    // Используйте ключевое слово `ref` для создания ссылки.
    match value {
        ref r => println!("Получили ссылку на значение: {:?}", r),
    }

    // Используйте `ref mut` аналогичным образом.
    match mut_value {
        ref mut m => {
            // Получаем ссылку. Её нужно разыменовать,
            // прежде чем мы сможем что-то добавить.
            *m += 10;
            println!("Мы добавили 10. `mut_value`: {:?}", m);
        },
    }
}

#[non_exhaustive]

Полная проверка при сопоставлении с образцом — очень полезный инструмент, позволяющий обнаружить определенные ошибки во время компиляции, проверяя, все ли комбинации некоторых значений включены и учтены в исходном коде.

Атрибут #[non_exhaustive] в Rust используется для того, чтобы предотвратить исчерпывающее (exhaustive) сопоставление с образцом (matching) в других модулях.

Это значит, что при использовании этого атрибута другие модули не смогут предположить, что они знают все варианты перечисления (enum) или все поля структуры, даже если они перечислены.

Это позволяет разработчикам библиотеки добавлять новые варианты перечислений или новые поля структур в будущем без нарушения обратной совместимости.

the-non_exhaustive-attribute

2008-non-exhaustive

using-non_exhaustive-for-non-exhaustive-rust-structs

Например, это очень плохой код:

fn grant_permissions(role: &Role) -> Permissions {
    match role {
        Role::Reporter => Permissions::Read,
        Role::Developer => Permissions::Read & Permissions::Edit,
        _ => Permissions::All, // anybody else is administrator 
    }
}
fn main(){}

Используя полноту, код можно изменить таким образом, чтобы он прерывался во время компиляции всякий раз, когда добавляется новый Role вариант:

fn grant_permissions(role: &Role) -> Permissions {
    match role {
        Role::Reporter => Permissions::Read,
        Role::Developer => Permissions::Read & Permissions::Edit,
        Role::Admin => Permissions::All, 
    }
}
fn main(){}

Структуры:

struct Address {
    country: Country,
    city: City,
    street: Street,
    zip: Zip,
}
// плохо:
impl fmt::Display for Address {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "{}", self.country)?;
        writeln!(f, "{}", self.city)?;
        writeln!(f, "{}", self.street)?;
        write!(f, "{}", self.zip)
    }
}
// хорошо:
impl fmt::Display for Address {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Self {
            country,
            city,
            street,
            zip,
        } = self;
        writeln!(f, "{country}")?;
        writeln!(f, "{city}")?;
        writeln!(f, "{street}")?;
        write!(f, "{zip}")
    }
}
fn main(){}

Тип fn (имя связанное с функцией) называется указателем на функцию.

fn main() {
    //Можно объявить имя, связанное с указателем на функцией
    let f_out: fn(i32,i32) -> i32; 
    let f_show: fn(i32,i32); 

    f_out = get_sum;
    f_show = show_sum;

    println!("сумма чисел: {}",f_out(10, 6));

    println!("----------\n");

    f_show(10, 6);
}

Function pointers

let my_f:fn(i32)->i32 = plus_one;

fn общий тип указателя на функцию (не путать с трейтом замыкания Fn)

Тип fn называется указателем на функцию.

Указатели на функции реализовать все три замыкающих трейта (Fn, FnMut и FnOnce), так что вы всегда можете передать указатель на функцию в качестве аргумента для функции, ожидающей замыкание.

Лучше всего писать функции, используя общий тип fn и одну из характеристик закрытия, чтобы ваши функции могли принимать либо функции, либо закрытия.


fn main(){
 fn add_one(x: i32) -> i32 {
    x + 1
 }
 // Ф-ция высшего порядка
 fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
 }

 // my_f указатель на функцию
 let my_f:fn(i32)->i32 = add_one;
 println!("my_f = {:p}", my_f);// my_f = 0x55c21df2db60

 // использование ф-ции fn:
 let answer = do_twice(add_one, 5);
 println!("The answer is: {}", answer);

 // использования замыкания closure:
 let closure = |x| {x+1};
 let answer = do_twice(closure, 5);
 println!("The answer is: {}", answer);
}

Function pointers

Имя связанное с указателем на функцию

fn main(){
  fn plus_one(i:i32)->i32{
      i+1
  }
    
  let my_f:fn(i32)->i32 = plus_one;
  println!("my_f = {:p}", my_f);// my_f = 0x55c21df2db60

  let six = my_f(5);
  println!("{}",six);
}

let f: fn(i32,i32) -> i32;

указатель на ф-цию с возвращаемым типом


fn main(){
 fn foo(x:i32,y:i32)-> i32{ x+y }    
 let f: fn(i32,i32) -> i32 = foo;
 print!("{}",f(4,5));//9
}

Обобщение + замыкание + Fn + PhantomData

// Статическая диспетчеризация
fn call_with_one<F>(func: F) -> usize where F: Fn(usize) -> usize {
    func(1)
}
// Динамическая диспетчеризация
fn call_with_one_dyn<T>(func: &T) -> usize where T: Fn(usize) -> usize {
    func(1)
}
// Обобщение аргументов ф-ции
fn call_with_one_t<T,N,M>(func: T,n:N) -> M
    where T: Fn(N) -> M  {
    func(n)
}
use std::marker::PhantomData; // через PhantomData прокидываем и тягаем за собой в объекте типы (свойство state)
struct  Cacher<N,M,T> where T:std::ops::Fn(N)->M
{
    calculation:  T,
    state: std::marker::PhantomData<(N,M)>
}
impl <T>Cacher<usize,usize,T>  where T:std::ops::Fn(usize)->usize{}
impl <T>Cacher<u32,u32,T>  where T:std::ops::Fn(u32)->u32{}
impl <N,M,T>Cacher<N,M,T>  where T:std::ops::Fn(N)->M{
    fn new(func:T)->Self{
        Cacher{  calculation:func,  state:PhantomData  }
    }
}
fn main(){
    let closure:fn(usize) -> usize = |x:usize| {x * 2 } ;
    let closure:_ = |x:_| x * 2;
    assert_eq!(call_with_one(closure), 2);
    assert_eq!(call_with_one_dyn(&closure), 2);
    assert_eq!(call_with_one_t(closure,1), 2);

    let casher:_ = Cacher{calculation:closure,state: PhantomData};
    let casher:Cacher<usize,usize,fn(usize) -> usize > = Cacher::new(closure);
    let casher:Cacher<_,_,fn(_) -> _ > = Cacher::new(closure);
    println!("{}",(casher.calculation)(1));
    // Использование реализации для u32
    let closure:fn(u32) -> u32 = |v:u32|->u32{1u32};
    let casher:Cacher<_,_,fn(_) -> _ > = Cacher::new(closure);
    println!("{}",(casher.calculation)(1));
}

Главная задача функции - это абстрагирование, когда мы даем имя для блока кода, то мы выносим его в абстракцию, и избавляемся от делатей реализации этого блока кода, все что нам теперь нужно для реализации поведения это имя функции.

Когда ф-ция ничего не возвращает -> то она все равно возвращает единичный кортеж () и это все равно что явно вернуть его ->()

trait Fn<Args>: FnMut<Args> ....

trait FnMut<Args>: FnOnce<Args>....

trait FnOnce<Args> - не требует реализации каких-либо других trait

Это означает, что замыкание, которое реализует Fn, также реализует FnMut и FnOnce.

Аналогично замыкание, которое реализует FnMut, также реализует FnOnce.

И, это значит, что если функция принимает FnOnce в качестве аргумента, она также может принимать Fn вместо этого (потому что Fn также реализует FnOnce) или FnMut

Функции высшего порядка - это функции которые оперируют другими функциями (переданными через аргументы) - для ФП стиля

fn main(){
   let res:Result<i32,std::io::Error> =  (|| { Ok(1) })();
   println!("{:?}",res);
}

Primitive Type never !

Тип который не возвращается никогда diverges

primitive.never

the-never-type-that-never-returns

Расходящиеся функции. Функции которые не возвращают управление наз. diverges

fn foo() {
    let x:! = return;
}
fn main(){}

Значение расходящейся функции может быть использовано как значение любого типа:

fn main(){
  let x: i32 = diverges();
  let x: String = diverges();
}

fn test(){
    struct Point;
    enum Void {}
    fn foo(void: Void) -> Vec<Point> {
        match void {
            _ => vec![]
        }
    }
}
fn main(){}

fn server_loop() -> Result<!, ConnectionError> {
    loop {
        let (client, request) = get_request()?;
        let response = request.process();
        response.send(client);
    }
}
fn main(){}

атрибут #[must_use]

Используется для указания того, что значение, возвращаемое функцией или создаваемое типом, не должно быть проигнорировано. Это помогает избежать ошибок, когда важные результаты операций остаются неиспользованными.

#[must_use]
fn important_calculation() -> i32 {
    42
}
#[must_use = "Этот результат важен, пожалуйста, не игнорируйте его"]
fn another_important_calculation() -> i32 {
    10
}
fn main() {
    important_calculation(); // Предупреждение: результат вызова функции игнорируется
}

concat_idents! - макрос объединяет идентификаторы в один идентификатор

#![feature(concat_idents)]

fn foobar() -> u32 { concat_idents!(foo, bar2)() }
fn foobar2() -> u32 { 23 }

fn main() {
    if concat_idents!(foo, bar)() > 20 {
       assert!(true);
    }else{
        assert!(false);
    }
}

несколько способов определить функцию

const fn

Есть несколько способов определить функцию в Rust:

  • обычная функция с fn,
  • небезопасная функция с unsafe fn,
  • внешняя функция с extern fn.
  • const fn (для определения размерности массива во время компиляции)
fn main(){
 const fn get_size(x: usize) -> usize {
    x * 2usize
 }
 const TEN: usize = get_size(5usize); // Это будет выполнено во время компиляции
 let arr:[i32;TEN] = [1,2,3,4,5,6,7,8,9,10];
}

A const fn может вызываться как обычная функция, но также может использоваться в любом постоянном контексте. Когда это так, она оценивается во время компиляции, а не во время выполнения.

const fn

Теперь в const fn можно создавать, передавать и приводить указатели на функции. Однако их вызов в const fn всё ещё запрещён. Преимущества: такие таблицы можно использовать для реализации интерпретаторов или других compile-time механизмов.

const fn create_function_table() -> [fn(i32) -> i32; 2] {
    // Определяем массив указателей на функции
    [add_one, subtract_one]
}

const fn add_one(x: i32) -> i32 {
    x + 1
}

const fn subtract_one(x: i32) -> i32 {
    x - 1
}
fn main() {
    let table = create_function_table();
    let result = (table[0])(10); // Вызов функции add_one через таблицу
    println!("{}", result); // Выводит 11
}

const fn

Теперь можно добавлять ограничения на типы через trait bounds для параметров в const fn. Ранее допускалось только ограничение Sized Преимущества: расширяет возможности const fn, позволяя работать с типами, удовлетворяющими определённым ограничениям (например, Copy, Default и т.д.).

Теперь в const fn можно использовать объекты типа dyn Trait. Это полезно для создания указателей на типы, реализующие определённые трейты.

Теперь аргументы и возвращаемые значения const fn могут быть impl Trait (определяемый тип)

const fn copy_and_double<T: Copy>(x: T, y: T) -> (T, T) {
    (x, y)
}
fn main() {
    const PAIR: (i32, i32) = copy_and_double(5, 10);
    println!("{:?}", PAIR); // Выводит (5, 10)
}

const fn create_dyn_trait() -> &'static dyn MyTrait {
    &MyStruct(42) as &dyn MyTrait
}
fn main() {
    let obj = create_dyn_trait();
    println!("{}", obj.value()); // Выводит 42
}

const fn get_closure() -> impl Fn(i32) -> i32 {
    |x| x * 2
}
fn main() {
    let closure = get_closure();
    println!("{}", closure(5)); // Выводит 10
}

const в fn

generics

fn double<const N: i32>() {
    println!("doubled: {}", N * 2);
}

const SOME_CONST: i32 = 12;

fn example() {
    // Example usage of a const argument.
    double::<9>();
    double::<-123>();
    double::<{7 + 8}>();
    double::<SOME_CONST>();
    double::<{ SOME_CONST + 5 }>();
}
fn main(){
   example() ;
}

Решение конфликта имен

 type N = u32; // Псевдоним типа
 struct Foo<const N: usize>;

 // Следующее является ошибкой, так как `N` интерпретируется как псевдоним типа `N`
 fn foo<const N: usize>() -> Foo<N> { todo!() } // ❌ ERROR

 // Можно исправить, заключив его в фигурные скобки, чтобы он интерпретировался как константный параметр `N`:
 fn bar<const N: usize>() -> Foo<{ N }> { todo!() } // ✅ ok
  • передача по не мутабельному значению
  • передача по мутабельному значению
  • передача по не мутабельной ссылке
  • передача по мутабельной ссылке
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}
// moving
fn birthday_immutable(person: Person) -> Person {
    Person {
        name: person.name,
        age: person.age + 1,
    }
}
// shared
fn birthday_l_immutable(person: &Person) -> Person {
    Person {
        name: person.name.clone(),
        age: person.age + 1,
    }
}
// mut moving
fn birthday_mutable(mut person: Person) -> Person {
    person.age += 1;
    person
}
// mut shared
fn birthday_l_mutable(person: &mut Person){
    person.age += 1;
}

fn main() {
    let alice1 = Person { name: String::from("Alice"), age: 30 };
    println!("Alice 1: {:?}", alice1);
    let alice2 = birthday_immutable(alice1);
    println!("Alice 2: {:?}", alice2);
    let alice3 = birthday_mutable(alice2);
    println!("Alice 3: {:?}", alice3);
    
    let mut alice4 = Person { name: String::from("Alice"), age: 30 };
    birthday_l_mutable(&mut alice4);
    println!("Alice 4: {:?}", alice4);
    
    let alice5 = birthday_l_immutable(&alice4);
     println!("Alice 5: {:?}", alice5);
}

Деструкция структуры в аргументах функции

struct S{
    x:u8,
    y:u8
}
fn main() {
    let obj:S = S{x:9u8,y:3u8};
    f(obj);
}
fn f( S { x,y }: S){
    println!("S=({},{})",x,y);
}

Способы передачи структуры в функцию

  • move перемещением
  • borrowing заимствованием по ссылке
  • imutability изменчиво
  • mutability неизменчиво
#[derive(Debug,Clone)]
struct MyStruct;
impl MyStruct{
    fn method(&mut self){ println!("{:?}",self);}
}
fn test(a:&mut MyStruct){}
fn move_fn(a:MyStruct){}
fn move_move_fn(a:MyStruct)->MyStruct{a}
fn borrowing_imutability_fn(a:&MyStruct){
    move_fn((*a).clone()); // разименовывать и перемещать клонированную версию
}
fn borrowing_mutability_fn(a:&mut MyStruct){
    /*можно использовать по mutability ссылке так как это не взятие еще одной ссылки это одна и таже ссылка*/
    let a2:&mut MyStruct = a;
    a.method();
    borrowing_imutability_fn(a);
    move_fn((*a).clone()); // или разименовывать и перемещать клонированную версию
}
fn move_mutability_fn(mut a:MyStruct){
    a.method();
}
fn move_mutability_move_fn(mut a:MyStruct)->MyStruct{
    a.method();
    a
} 
fn main() {
    let t:MyStruct = MyStruct;
    move_fn(t.clone());// переместим клонированную версию
    move_fn(t);// перемещение t. больше t ее использовать нельзя
    
    let mut t:MyStruct = MyStruct;
    t = move_move_fn(t);// двойное перемещение
    let new_t:MyStruct = t;
    
    let t:MyStruct = MyStruct;
    borrowing_imutability_fn(&t);// заимствование по не изменяемой ссылке
    let new_t:MyStruct = t;
    
    let mut t:MyStruct = MyStruct;
    borrowing_mutability_fn(&mut t);// заимствование по изменяемой ссылке
    let new_t:MyStruct = t;
  
    let t:MyStruct = MyStruct;
    move_mutability_fn(t);// перемещение данных в мутабельное состояние
    
    let mut t:MyStruct = MyStruct;
    t = move_mutability_move_fn(t);// двойное перемещение c этапом мутации
    let new_t:MyStruct = t;
}

Пример использования Fn в структуре

struct MyClosure<F> {
    data: (u8, u16),
    func: F,
}
impl<F> MyClosure<F> where F: Fn(&(u8, u16)) -> &u8 {
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}

fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }

fn main() {
    let my_c = MyClosure { data: (0, 1), func: do_it };
    assert_eq!(&0,my_c.call());
    // или
    let f:fn(&(u8, u16)) -> &u8 = do_it;
    let f:fn(&(u8, u16)) -> &u8 = |data:&(u8, u16)| -> &u8 { &data.0 };
    let my_c = MyClosure { data: (0, 1), func: f };
    assert_eq!(&0,my_c.call());   
}

Замыкание

статическая Fn и динамическая &Fn диспетчеризация

Это мономорфизация типажем т.е. статическая диспетчеризация:

fn call_with_one<F>(some_closure: F) -> i32
    where F : Fn(i32) -> i32 {
    some_closure(1)
}
fn main(){
 let answer = call_with_one(|x| x + 2);
 assert_eq!(3, answer);
}

Это динамическая диспетчеризация так как &Fn это типаж-объект:

fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
    some_closure(1)
}
fn main(){
 let answer = call_with_one(&|x| x + 2);
 assert_eq!(3, answer);
}
  • Замыкание соответсвует типижу FnOnce потому что завладевает, перемещает moving

  • Замыкание соответсвует типижу FnMut потому что изменеят в замыкании переменную

  • Замыкание соответсвует типижу Fn потому что нет мутации и нет перемещения move

  • каждый трейт Fn так же является трейтом FnMut, так что везде, где вы можете безопасно вызывать мутабельную функцию, вы так же можете вызывать и немутабельную

  • трейты Fn и FnMut так же реализуют трейт FnOnce, потому что любой контекст, в котором вы можете гарантировать то, что функция будет вызвана только раз

Замыкание соответствует типажу FnOnce потому что перемещает тип moving. Следовательно, вызвать ее можно только один раз, после переменная удалится

fn main(){
 let name1 = String::from("Alice");
 let welcom = move || {
     let mut name2 = name1;
     name2 += " and Bob";
     println!("Welcome, {}", name2);
 };
 // welcom();
 // welcom();// Error  
 call_FnOnce(welcom);
}

Замыкание соответствует типажу Fn потому что нет мутации и нет перемещения move. А типаж Fn реализует сразу и FnMut и FnOnce

fn main(){
 let name:&str = "Gogo";
 let visit1 = || {
    println!("Hello {}", name);
 };
 call_Fn(&visit1);
 call_FnMut(&visit1);
 call_FnOnce(&visit1);
}

Замыкание соответствует типажу FnMut потому что изменяет в замыкании переменную. А FnMut так же является трейтом FnOnce

fn main(){

 let mut count = String::new();
 let visit2 = || {
    count.push('1');
    println!("You are visitor #{}", count);
 };
 call_FnMut(visit2);
 call_FnMut(visit1);// ограничения FnMut сильнее Fn поэтому Fn можно использовать
}

Замыкание соответствует сразу Fn и FnMut и FnOnce

fn main(){
 let say_hi = {
        let name = String::from("Alice");
        move || println!("Hello, {}", name)
 };
 call_Fn(&say_hi);
 call_FnMut(&say_hi);
 call_FnOnce(&say_hi);
}
fn call_Fn<F>(f: F) where F: Fn() {
    for _ in 1..6 {
        f();
    }
}
fn call_FnMut<F>(mut f: F) where F: FnMut() {
    for _ in 1..6 {
        f();
    }
}
fn call_FnOnce<F>(f: F) where F: FnOnce() {
    f();
}
  • FnOnce тип для одного вызова, функция удаляется, завладевает
  • Fn многократный не мутирующий
  • FnMut многократный мутирующий

trait.FnMut

fn foo(name:&str)-> String{ name.to_string()}

fn register_escape_fn<F: 'static + Fn(&str) -> String + Send + Sync>( escape_fn: F, name:&'static str ){
  println!("register_escape_fn: {}", escape_fn(name));
  println!("register_escape_fn: {}", escape_fn(name));
}

fn register_escape_fnonce<F: 'static + FnOnce(&str) -> String + Send + Sync>( escape_fn: F, name:&'static str ){
  println!("register_escape_fnonce: {}", escape_fn(name));
  // println!("register_escape_fnonce: {}", escape_fn(name));//  use of moved value: `escape_fn`
}

fn register_escape_fnmut<F: 'static + FnMut(i32) -> i32 + Send + Sync>( mut escape_fn: F, value:i32 ){
  println!("register_escape_fnmut: {}", escape_fn(value));
}
fn main(){
 let value:&str="hi";
 register_escape_fn(|x|{x.to_string()},value);
 register_escape_fn(foo,value);

 register_escape_fnonce(|x|{x.to_string()},value);
 register_escape_fnonce(foo,value);

 let mut value:i32=0;
 register_escape_fnmut(|mut x|{ x += 2;x },value);
}

Пример использования FnOnce

#[derive(Debug,PartialEq)]
enum Test<X>{
    Some(X),
    None
}
impl<X> Test<X> {
    pub fn map<Y, F: FnOnce(X) -> Y>(self, f: F) -> Test<Y> {
        match self {
            Test::Some(x) => Test::Some(f(x)),
            Test::None => Test::None
        }
    }
}
fn main() {
     let t = Test::Some(3);
     let res = t.map(|val:i32| -> i32 { val + 7 });
     assert_eq!(Test::Some(10),res);
     println!("{:?}",res);// 10
}

fn

clousure

Box< dyn Fn() > 
fn register_escape_fn<F>(escape_fn: F, name:&'static str) 
  where F:Fn(&str) -> String,
             F:'static,
             F:Send + Sync 
{
  println!("register_escape_fn: {}", escape_fn(name));
}

fn main(){
    register_escape_fn(|x|{x.to_string()},"closure");

    fn foo(name:&str)-> String{ name.to_string()}
    register_escape_fn(foo,"fn");

    let f:Box<dyn Fn(&str) -> String + Send + Sync> = Box::new(|x:&str|{x.to_string()});
    register_escape_fn(f,"dyn Fn");
}

impl Trait и замыкания

Возврат замыканий по значению

Для того чтобы вернуть что-то из функции, Rust должен знать, какой размер имеет тип возвращаемого значения. Можем передать через ссылку. Так как у нас используется ссылка, то мы должны задать ее время жизни замыкание заимствует свое окружение. И в этом случае наше окружение представляет собой выделенную в стеке память, содержащую значение связанной переменной num - 5. Из-за этого заем имеет срок жизни фрейма стека. Так что, когда мы вернем это замыкание, то вызов функции будет завершен, а фрейм стека уйдет, и наше замыкание захватит окружение, содержащее в памяти мусор!

Мы можем возвращать замыкания по значению, как и любой другой тип! Так как Rust требует размер возвращаемых значений мы можем передать только ссылку на ф-цию.

move делает наш Fn что бы он создавал новый фрейм стека

Box дает известный размер замыкания

fn returns_closure_2() -> Box<dyn Fn(i32) -> i32> {
    let num = 5;
    Box::new(move |x| x + num)
}
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}
fn returns_fn<MY_F>(f:MY_F) -> impl Fn(i32) -> i32 where MY_F:Fn(i32) -> i32 {
    f
}
fn returns_fn_2() -> impl Fn(i32) -> i32 {
    fn some_fn(x:i32)->i32{
      x+1
    } 
    some_fn   
}
fn some_fn(x:i32)->i32{
    x+1
}
fn main() {
   let f = returns_closure_2();
   let answer = f(1);
   assert_eq!(6, answer);

  let f_closure = returns_closure();
  assert_eq!(f_closure(2),3);
  
   let f_fn = returns_fn(some_fn);
  assert_eq!(f_fn(2),3);
     
   let f_fn = returns_fn_2();
  assert_eq!(f_fn(2),3);
}

impl Fn и dyn Fn применяет переданную функцию

проход по дереву каталогов

use std::io::Write;
use std::path::{Path,PathBuf};
use std::error;

fn walk(root: impl AsRef<Path>, callback: impl Fn(&Path)) -> Result<Vec<PathBuf>, Box<dyn error::Error>> {
    let root = root.as_ref().to_path_buf();
    let mut tasks = vec![root];

    while let Some(path) = tasks.pop() {
        if path.is_file() {
            callback(&path);
        } else if path.is_dir() {
            for dir in std::fs::read_dir(&path)? {
                tasks.push(dir?.path());
            }
        }
    }
    Ok(tasks)
}
// или
use std::fs::{self, DirEntry};
fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                visit_dirs(&path, cb)?;
            } else {
                cb(&entry);
            }
        }
    }
    Ok(())
}
fn main(){
    let mut path: PathBuf = PathBuf::new();
    path.push(r"./adder/picture");
    if path.exists() { 
       walk(&path, |path| {println!("{:?}", path.display())}); 
       // или
       let f = |entry:&DirEntry| {println!("{:?}", entry.path().display());};
       visit_dirs(&path,  &f); 
    }
}

dyn Fn

fn main(){
    let mut v_func:Vec<Box<dyn Fn(usize)->usize>> = vec![];
    v_func.push(Box::new(|val|{val+1usize}));
    v_func.push(Box::new(|val|{val+2usize}));
    
    let f:fn(usize)->usize = |val:usize| -> usize {val*val};
    v_func.push(Box::new(f));
    
    for (index,f) in v_func.iter().enumerate(){
       println!("{}",f(index));  // 1 3 4
    } 
}
Box<dyn Fn()>
fn bare(x: &i32) -> i32 { 2 * *x }
let generic: Box<dyn for<'a> Fn(&'a i32) -> i32> = Box::new(bare);

fn bare2(x: i32) -> i32 { 2 * x }
let generic: Box<dyn Fn(i32) -> i32> = Box::new(bare2);

fn bare3(x: Box<i32>) -> i32 { 2 }
let generic: Box<dyn Fn(Box<i32>) -> i32> = Box::new(bare3);

fn bare4(x: Box<dyn std::any::Any + Send + 'static>) -> i32 { 2 }
let generic: Box<dyn Fn(Box<dyn std::any::Any + Send + 'static>) -> i32> = Box::new(bare4);
fn main(){}

-> Box<Fn()> возвращаемое значение функция в Box() для размера возвращаемого типа

the-trait-stdopsfn-is-not-implemented-for-closure-but-only-with

fn main(){
    let s = Some("xyz".to_string());
    let foo = make_foo(&s);
    println!("{:?}", foo());
 //или unstable
    use std::boxed::FnBox;
    let s = Some("xyz".to_string());
    let foo = Box::new(|| s) as Box<FnBox() -> Option<String>>;
    println!("{:?}", foo());
}
fn make_foo<'a>(s: &'a Option<String>) -> Box<Fn() -> Option<&'a str> + 'a> {
    Box::new(move || s.as_ref().map(|s| &**s)) as Box<Fn() -> Option<&'a str> + 'a>
}

доступ к приватной функции модуля

mod controller{  
    fn my_private(v:i32)->bool{
        true
    }

  #[cfg(test)]
   pub mod ex{
    use super::*;
        pub fn extend_visible()->Box<dyn Fn(i32)->bool +Send + Sync>{
            Box::new(super::my_private)  
        }
    }
}

#[cfg(test)]
pub mod engine{
use super::controller::ex::extend_visible;
    #[test]
    pub fn test_private_method(){
       let func_private = extend_visible();
       assert!(func_private(2));
    }
}


// вариант 2 с обьектом
// Выполнение метода теста в контекте видимости приватного метода 
// необходимо динамически определить принадлежность метода в процесе выполнения
mod controller2{
    pub struct A{
        pub data:i32
    }
    impl A{
        fn private(&self)->i32{
          self.data
        }
    }
    pub fn extend_obj<F: 'static + Fn(Box<dyn Fn(&A)->i32 + Send + Sync>) + Send + Sync>(f:F){     
        f(Box::new(A::private));
    }
    fn private()->i32 {       
      88
    }
    pub fn extend<F: 'static + Fn(Box<dyn Fn()->i32 + Send + Sync>) + Send + Sync>(f:F){
        f(Box::new(self::private));
    }
}

#[cfg(test)]
mod engine2 {
use super::*;
    #[test]
    fn test_private_obj() {
        fn inj(func_private:Box<dyn Fn(&controller2::A)->i32 + Send + Sync>){
          let a = controller2::A{data:88};
          let res = func_private(&a); 
          assert_eq!(88,res);
        }   
        controller3::extend_obj(inj);
    }   
     #[test]
    fn test_private() {
        fn inj(f:Box<dyn Fn()->i32 + Send + Sync>){
          let res = f();
          assert_eq!(88,res);
        }
        controller2::extend(inj);
    }
}

Closures

(a lambda expression)

Лямбда-выражения (анонимные функции), замыкания являются типажами

finding-closure-in-rust

closures

Анонимная функция — это функция без имени. Её нельзя вызвать по имени, а можно только присвоить переменной или передать в другую функцию.

Лямбда — это просто короткий синтаксис для создания анонимной функции. Почти всегда это одно и то же, но "лямбда" подразумевает лаконичную запись. Лямбда — это вид анонимной функции.

Замыкание (Closure) — это не тип функции, а её свойство. Это когда функция «помнит» и имеет доступ к переменным из той области, где она была создана, даже если та область уже не существует. Анонимная ф-ция станет замыканием если захватит в свою область внешнюю переменную.

Замыкания могут владеть данными, функции — нет.

От того как используются данные внутри замыкания зависит какой трейт она будет реализовывать FnOnce, FnMut либо Fn, а не от аргументов замыкания

Если замыкание захватывает ссылку то у него будет тоже lifetime

Отличие от обычных функций для замыкания мы не обязаны указывать типы аргументов и возвращаемого значения.

fn main(){
    let some_var = 9;
    let plus_one = |x| x + some_var;
    let plus_one = |x: i32| -> i32 { x + 1 };
    let plus_one = || 1;
}

Closures может быть приведена к типу Function pointers только если Closures не захватывает внешних переменных!!!

fn main(){
// т.е. есть такая ф-ция 
    fn do_twice(f: fn(i32) -> i32){}
// она может принять closure: (тут произойдет приведение closure к типу Function pointers )
   let closure = |x| {x+1};
   let answer = do_twice(closure, 5);
// но если closure с захватом переменной то не выйдет:
   let some_var = 8;
   let closure = |x| {x+some_var};
   let answer = do_twice(closure, 5);
}

использования асинхронных замыканий


async || {}

Асинхронная операция, которую мы хотим выполнить внутри замыкания

#[tokio::main]
async fn main() {
    println!("Начинаем асинхронные замыкания!");

    // Создаем асинхронное замыкание
    let my_async_closure = async || {
        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        println!("Привет из асинхронного замыкания!");
        42 // Возвращаемое значение из замыкания
    };

    // Выполняем асинхронное замыкание
    let result = my_async_closure().await;
    println!("Результат из асинхронного замыкания: {}", result);

    println!("Завершаем асинхронные замыкания!");
}

использования асинхронных замыканий


async || {}
Использование асинхронных замыканий с итераторами/коллекциями
use futures::stream::{self, StreamExt}; // Для StreamExt и from_iter
use reqwest; // Для асинхронных HTTP-запросов

#[tokio::main]
async fn main() {
    let urls = vec![
        "https://www.example.com/".to_string(),
        "https://www.rust-lang.org/".to_string(),
        "https://docs.rs/".to_string(),
    ];
 
    // Используем асинхронное замыкание с `map` и `buffer_unordered` для параллельной обработки
    let results: Vec<String> = stream::iter(urls)
        .map(async |url| { // Асинхронное замыкание захватывает `url`
            println!("Запрос: {}", url);
            match reqwest::get(&url).await {
                Ok(response) => {
                    if let Ok(text) = response.text().await {
                        format!("{} - Успех, длина: {}", url, text.len())
                    } else {
                        format!("{} - Ошибка чтения тела", url)
                    }
                },
                Err(e) => format!("{} - Ошибка запроса: {}", url, e),
            }
        })
        .buffer_unordered(2) // Одновременно обрабатываем до 2 запросов
        .collect()
        .await;

    println!("\nРезультаты загрузки:");
    for result in results {
        println!("{}", result);
    }
}

использования асинхронных замыканий


async || {}
Сценарий использования с обработчиками событий (например, в веб-фреймворках)

// Упрощенный "фреймворк"
struct EventProcessor {
    // В реальном мире здесь будет что-то более сложное,
    // возможно, Vec<Box<dyn Fn(...) -> Pin<Box<dyn Future<Output = ()> + Send>>>>
}

impl EventProcessor {
    async fn process_event<F, Fut>(&self, handler: F)
    where
        F: Fn() -> Fut, // Обработчик принимает () и возвращает Future
        Fut: std::future::Future<Output = ()>, // Future возвращает ()
    {
        println!("Обработка события...");
        handler().await; // Выполняем асинхронный обработчик
        println!("Событие обработано.");
    }
}

#[tokio::main]
async fn main() {
    let processor = EventProcessor {};

    // Регистрируем асинхронный обработчик события с помощью async closure
    processor.process_event(async || {
        println!("Выполняется асинхронный обработчик...");
        tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
        println!("Асинхронный обработчик завершен.");
    }).await;

    println!("Готово!");
}

передача в ф-ию замыкания с возможностью мутировать захваченную переменную

fn apply<F>(f: &mut F, arg: i32)  -> i32
where
    F: FnMut(i32) -> i32,
{
    f(arg)
}
fn main() {
    let mut some_var = 8;
    let mut closure =|x| {some_var+=1;x+some_var};
    assert_eq!(14, apply(&mut closure, 5));
    assert_eq!(15, apply(&mut closure, 5));
}

Тип аргумента устанавливается при первом вызове и не меняется

Попытка вызвать замыкание, типы которого выводятся с двумя разными типами Компилятор дает нам эту ошибку :mismatched types

fn main(){
   let example_closure = |x| x;
   let s = example_closure(String::from("hello"));
   let n = example_closure(5); // :mismatched types
}

Все closure/замыкание/закрытие/лямбда уникальны

замыкание никогда не является тем же типом, что и другое замыкание, даже если сигнатура та же самая.

fn foo<F, G>(first: F, second: G) // с общим типом будет ошибка
where
    F: Fn() -> i32,
    G: Fn() -> i32,
{}

fn main() {
    let first_closure = || 9;
    let second_closure = || 9;
    foo(first_closure, second_closure);
}

move перемещение в замыкание

fn main() {
   let mut buff = String::new(); // String это Clone (если взять Copy то number просто скопируется в замыкание)
   {
        let mut plus_two = move |x:&str| { // move забрал во владение buff
            buff.push_str(x);
            println!("{:?}", &buff);  
        };
        plus_two("ss");
    }
   // println!("{:?}", &buff); // Error: borrow of moved value: `buff`
}

move

Для решения проблем со временем жизни, мы можем заставить замыкание захватить переменные по значению с помощью ключевого слова move

fn main() {
    let say_hi = { 
        let name_outer = String::from("Alice");
        /*
        // не работает, так как замыкание переживает захваченные значения
        || {
            // use by ref
            let name_inner = &name_outer;
            println!("Hello, {}", name_inner);
        }
        */
        // работает
        move || {
            let name_inner = &name_outer;
            println!("Hello, {}", name_inner);
        }
    };
    say_hi();
    say_hi();
}

Замыкания могут захватывать переменные:

  • по ссылке: &T
  • по изменяемой ссылке: &mut T
  • по значению: T

При передаче замыканий как аргумент в функцию надо явно указывать типаж захвата замыкания

  • Fn: замыкание захватывает по ссылке (&T)
  • FnMut: замыкание захватывает по изменяемой ссылке (&mut T)
  • FnOnce: замыкание захватывает по значению (T)
fn test_fn_one<F: FnOnce()>(f: F) {
   println!("FnOnce");
}
fn test_fn_mut<F: FnMut()>(f: F) {
   println!("FnMut");
}
fn test_fn<F: Fn()>(f: F) {
   println!("Fn");
}

Замыкание которое захватывает ссылку:

fn main(){
    let mut text = "Hello".to_string();
    
    let closure = || println!("{}", &text);
    
    // При вызове замыкания используется shared ссылка
    // поэтому мы не можем вызвать его после создания уникальной mut ссылки при вызове `text.push('a')`
    // text.push('a'); ERROR
    
    closure();
    
    text.push('a');
}

fn main(){
  // Тип без возможности копирования.
 let movable = Box::new(3);

  // `mem::drop` требует `T`, так что захват производится по значению.
  // Копируемый тип будет скопирован в замыкание, оставив оригинальное
  // значение без изменения. Некопируемый тип должен быть перемещён, так что
  // `movable` немедленно перемещается в замыкание.
  let consume = || {
      println!("`movable`: {:?}", movable);
      mem::drop(movable);
  };

  // `consume` поглощает переменную, так что оно может быть вызвано только раз.
  consume();
  //consume();
}

Замыкание - это пара из окружения(Env) и функции fn без контекста

Компилятор в замыкании выбирает способ использования переменных исходя из того как мы используем переменную в замыкании

Пример анонимной структуры создаваемой компилятором при распознавании способа использования переменной

struct Closure<'a, 'b> {
    xs: Vec<i32>, // для варианта использования по значению
    ys: &'a Vec<i32>, // для варианта использования по не изменяемой ссылке
    zs: &'b mut Vec<i32>,// для варианта использования по изменяемой ссылке
}
impl<'a, 'b> FnOnce() for Closure<'a, 'b> {
    type Output = ();
    fn call_once(mut self) {
        drop(self.xs);// Если убрать drop то можно реализовать FnMut()
        println!("{}", ys.len());// при использовании только константных ссылок
        zs.push(10);// при использовании изменяемых методов zs: &'b mut Vec<i32>
    }
}
fn main(){}

fn main(){
    let color = String::from("green"); // (String это Clone, но если мы используем тип Copy то он будет скопирован и move не покажет себя)
    // Ф-ция завладевает по значению
        fn print(color:String){println!("`color`: {}", color);}
        print(color);
    // или замыкание завладевает через move
        let print = move || println!("`color`: {}", color);
        print();
       // println!("`color`: {}", color);// Error
// ------------------------------------------------- 
    // Замыкание заимствует &color (по не изменяемой ссылке)
       let print = || println!("`color`: {}", color);
       print();
       print();
// ------------------------------------------------- 
    // Замыкание заимствует &mut count так как count изменяется внутри (по изменяемой ссылке)
      let mut point = String::new();
      {
          let mut inc = || {
              point.push('.');
              println!("`point`: {}", point);
          };
          inc();// .
          inc();// ..
      }
      assert_eq!("..".to_string(),point); // тут есть доступ к point значит move небыло
    Но если специально переместить move:  `let mut inc = move || {....` то point удалится 
}

Решить ошибку компилятора, которая может произойти, когда вы пытаетесь передать замыкание, возвращающее Result

С помощью синтаксиса Turbofish  Ok::<_, Error>(())
use thiserror::Error;

#[derive(Error, Debug)]
enum Error {}

#[derive(Error, Debug)]
enum MainError {
    #[error("Sub error: {0}")]
    Sub(#[from] Error),
}

fn main() -> Result<(), MainError> {
    let r = your_wrapper(|| {
       println!("Inside wrapper");
       Ok::<_, Error>(())
    })?;

    println!("Following call");
    Ok(r)
}

Функцию тоже можно объявить внутри блока, но она не будет замыканием

fn fib(n: usize) -> usize {
    let mut cache = vec![0; n + 1];
    return fib_memo(&mut cache, n);
    
    fn fib_memo(cache: &mut Vec< usize>, n: usize) -> usize {
        if n == 0 || n == 1 {
         return 1;
        }
        if cache[n] == 0 {
            cache[n] =
            fib_memo(cache, n - 1) + fib_memo(cache, n - 2);
        }
        cache[n]
    }
}
fn main(){
   println!("{}",fib(5));
}

Выражение с замыканием

❌ Так неправильно:

(Каждый вызов замыкание будет перемешать вектор)

fn main(){
     let no_gc = vec!["C", "C++", "Rust"];         
     let has_gc = |lang: &str| -> bool {
            !no_gc.contains(&lang)
    };
}

✅ Правильно:

fn main(){
   let has_gc = {
// Нет смысла вектор перемещать в замыкание так как он будет геолацироваться в памяти каждый раз
// при вызове замыкания и уничтожаться каждый раз!
// И если замыкание ничего не замыкает т.е. пустой Environment то это просто функция получается
        let no_gc = vec!["C", "C++", "Rust"];
// Возвращает
// Реализует все три трейта FnOnce,FnMut,Fn так как ничего не делает со своим окружением ни удаляет ни изменяет
        move |lang: &str| -> bool {
            !no_gc.contains(&lang)
        }
    };
    assert!(has_gc("Java"));
}

Пример использования анонимных функций

fn map< T, R, F: FnMut(&T) -> R>(xs: &[T], mut f: F) -> Vec<R>  {
    let mut res = Vec::with_capacity(xs.len());
    for x in xs {
         let y = f(x);
         res.push(y);
    }
    res
}
fn zipmap< F: Fn(&T) -> R, T, R>(xs: &[T], fs: &[F]) -> Vec< R> {
    let iter = xs.iter().zip(fs);
    let mut res = Vec::with_capacity(iter.len()); // ^^
    for (x, f) in iter {
         res.push(f(x));
    }
    res
}
fn main(){
  let mut lymda = |v:&i32|->i32 {v*10};
  let res =  map(&[1,2,3],&mut lymda);
  //println!("{:?}",res);
  assert_eq!([10, 20, 30],&res[..]);
  
  let res =  map(&[1,2,3],|v|->i32 {v*10});
  assert_eq!([10, 20, 30],&res[..]);
  
  let lymda = |v:&i32|->i32 {
    v*10
  };
  let res =  zipmap(&[2,3,4],&[lymda,lymda,lymda]);
  assert_eq!([20, 30, 40],&res[..]);
}

Пример использования анонимных функций

Решение приведение типа ф-ции к адресам этих ф-ций для однотиного вызова (call по адресу в памяти НЕ ВИРТУАЛЬНЫЙ так работать не будет)

fn zipmap<F: Fn(&T) -> R, T, R>(xs: &[T], fs: &[F]) -> Vec<R> {
    let iter = xs.iter().zip(fs);
    let mut res = Vec::with_capacity(iter.len()); // ^^
    for (x, f) in iter {
         res.push(f(x));
    }
    res
}
fn next(x: &i32) -> i32 { x + 1 }
fn prev(x: &i32) -> i32 { x - 1 }

fn main(){
  let lymda = |v:&i32|->i32 {
    v*10
  };
  let res =  zipmap(&[2,3,4],&[lymda,lymda,lymda]);
  assert_eq!([20, 30, 40],&res[..]);
  
   let res =  zipmap(&[2,3,4],&[next,next,next]);
  assert_eq!([3, 4, 5],&res[..]);
  
  let res =  zipmap(&[2,3,4],&[prev,prev,prev]);
  assert_eq!([1, 2, 3],&res[..]);
  
  //let res =  zipmap(&[2,3,4],&[next, prev,next]);// mismatched types
  // Решение приведение типа ф-ции к адресам этих ф-ций для однотиного вызова
  let slice:&[fn(&i32) -> i32;3]=&[next, prev,next];
  let res =  zipmap(&[2,3,4],slice);
  assert_eq!([3, 2, 5],&res[..]);
  
  let fs: Vec<fn(&i32) -> i32> = vec![next, prev,next];
  let res =  zipmap(&[2,3,4],&fs);
  assert_eq!([3, 2, 5],&res[..]);
}

Эффективное применение замыкания

Улучшенный Cacher возвращает данные из HashMap

Реализация Fn функции с возможностью использовать разные типы

use std::thread;
use std::time::Duration;
use std::collections::HashMap;
// Примечание:Если то, что мы хотим сделать, не требует захвата значения из среды, мы можем использовать функцию, а не закрытие, где нам нужно что-то, что реализует Fn черту
 
struct Cacher<N,M>{
    calculation:Box<Fn(N)->M>,
    map:HashMap<u32, u32>
}

impl Cacher<u32,u32>{
    fn new<T:'static + Fn(u32) -> u32>(calculation: T) -> Cacher<u32,u32> {
        Cacher {
            calculation:Box::new(calculation),
            map:HashMap::new()
        }
    }
    fn value(&mut self, arg: u32) -> u32 {
        if self.map.contains_key(&arg){
            *self.map.get(&arg).unwrap()
        }else{
            let v = (self.calculation)(arg);
            self.map.insert(arg,v);
            v
        }
    }
}
fn simulated_expensive_calculation(intensity: u32) -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    intensity
}

fn main() {
    let intensity: u32 = 2;
    let random_number: u32 = 4;
/*
// №1 Вариант оптимизации с ф-цией
// Решает проблему первого if блока, ненужно вызывая функцию дважды.
// К сожалению, мы теперь вызываем эту функцию и ожидаем результата во всех случаях, включая внутренний if блок, который вообще не использует значение результата.
    let expensive_result = simulated_expensive_calculation(intensity);

// №2 Вариант с замыканием
// Решает проблему лишнего вызова
// Определить замыкание и сохраним его в переменной.
// Отработает вычисление только по требованию.
// Теперь дорогостоящий расчет вызывается только в одном месте, и мы выполняем этот код только там, где нам нужны результаты.
// К сожалению, мы вернули проблему первого вариант с вызовом замыкания дважды в первом if блоке
// Мы можем исправить через присвоение результата замыкание в локальную переменную, но этот метод может привести к лишнему коду
// Мы можем создать структуру, которая будет удерживать замыкание и результирующее значение вызова закрытия.
    let expensive_closure = |num:u32| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };
*/
// №3 Вариант со структурой удерживающей замыкание и результирующее значение вызова его
// Ленивая работа (pattern as memoization or lazy evaluation).
// Строка будет выполнять закрытие только в том случае, если нам понадобится результирующее значение, и оно будет кэшировать полученное значение
// Мы можем вызвать value метод столько раз, сколько хотим, или вообще не называть его, а дорогостоящий расчет будет выполняться максимум один раз.
    let mut lazy_evaluation_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    if intensity < 25 {
        println!(
            "Today, do {} pushups!",
            /*expensive_result*/
            /*expensive_closure(intensity)*/
            lazy_evaluation_result.value(intensity)
        );
        println!(
            "Next, do {} situps!",
            /*expensive_result*/
            /*expensive_closure(intensity)*/
            lazy_evaluation_result.value(intensity)
        );
    } else {
        if random_number == 3 {
// тут проблема при Вариант №1 так как мы неиспользуем результат этой ф-ции то зачем ее было вызывать ?
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                /*expensive_result*/
                /*expensive_closure(intensity)*/
                lazy_evaluation_result.value(intensity)
            );
        }
    }
}

Эффективное применение замыкания

Улучшенный Cacher возвращает данные из HashMap

Реализация для одного типа u32

struct Cacher<T> where T: Fn(u32) -> u32
{
    calculation: T,
    value: Option<u32>,
}

impl<T> Cacher<T> where T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }
// Производим вычисление через замыкание только если self.value  == None иначе возвращаем   self.value
    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            },
        }
    }
}
fn main(){}

use std::borrow::Cow;
struct CacherStr<N,M,T> where T:std::ops::Fn(N)->M {
    calculation:  T,
    state: std::marker::PhantomData<(N,M)>,
    map:HashMap<Cow<'static, str>, Cow<'static, str>>,
}
impl <T> CacherStr< Cow< 'static, str>,Cow< 'static, str>,T> where T: Fn(Cow< 'static, str>) -> Cow< 'static, str> {
    fn new_str (calculation: T) -> CacherStr< Cow< 'static, str>,Cow< 'static, str>,T> {
        CacherStr {
            calculation,
            state: PhantomData,
            map:HashMap::new()
        }
    }
    fn value<K>(&mut self, arg: K) -> Cow< 'static, str>  where K: Into< Cow< 'static, str>>+Copy{
        if self.map.contains_key(&arg.into()){
            (*self.map.get(&arg.into()).unwrap()).clone()
        }else{
            let v = (self.calculation)(arg.into());
            self.map.insert(arg.into(),v.clone());
            v
        }
    }
}
fn main(){}

Вывод типа не может определить тип замыкания при вызове функции с аргументом, ограниченным трейтом, реализованным для замыкания, которое принимает ссылочный аргумент

issues/24680

struct Bar<'a, 'b> {
    a: &'a (),
    b: &'b ()
}

struct Qux<'a> {
    a: &'a ()
}

struct Foo<'a> {
    a: &'a ()
}
trait Handler {
    fn handle< 'a, 'k>(&'a self, Bar< 'a, 'k>, Qux< 'a>) -> Foo< 'a>;
}
impl<F> Handler for F
    where F: for< 'a, 'k> Fn(Bar< 'a, 'k>, Qux< 'a>) -> Foo< 'a> + Sync + Send {
    fn handle<'a, 'k>(&'a self, req: Bar< 'a, 'k>, res: Qux< 'a>) -> Foo< 'a> {
        self(req, res)
    }
}
fn call_handler< H: Handler>(h: H) {
    println!("call_handler");
    h.handle(Bar { a: &(), b: &() }, Qux { a: &() });
}
fn call_closure< F: for< 'a, 'k> Fn(Bar< 'a, 'k>, Qux< 'a>) -> Foo< 'a> + Sync + Send>(f: F) {
    println!("call_closure");
    f(Bar { a: &(), b: &() }, Qux { a: &() });
}
fn wrapped_call_handler< F: for< 'a, 'k> Fn(Bar< 'a, 'k>, Qux< 'a>) -> Foo< 'a> + Sync + Send>(f: F) {
    println!("wrapped_call_handler");
    call_handler(f)
}
fn main() {
    // Эти работают
    call_closure(|_bar, qux| Foo { a: qux.a });
    wrapped_call_handler(|_bar, qux| Foo { a: qux.a });

    // Эти не работают
    // 'Type must be known'
    //call_handler(|_bar, qux| Foo { a: qux.a });
    // error: type mismatch resolving `for< 'a,'k> < [closure <anon>:52:18: 52:57] as core::ops::FnOnce<(Bar< 'a, 'k>, Qux<'a>)>>::Output == Foo< 'a>`:
    // expected bound lifetime parameter 'a
    //call_handler(|_bar: Bar, qux: Qux|  Foo { a: qux.a });
}

Пример возврата из ф-ции замыкания

fn returns_a_closure(input: &str) -> impl FnMut(i32) -> i32 {
    match input {
        "double" => |mut number| {
            number *= 2;
            println!("Doubling number. Now it is {number}");
            number
        },
        "triple" => |mut number| {
            number *= 3;
            println!("Tripling number. Now it is {number}");
            number
        },
        _ => |number| {
            println!("Sorry, it's the same: {number}.");
            number
        },
    }
}
 
fn main() {
    let my_number = 10;
 
    let mut doubles = returns_a_closure("double");   
    let mut triples = returns_a_closure("triple");   
    let mut does_nothing = returns_a_closure("HI");  
 
    let doubled = doubles(my_number); // Doubling number. Now it is 20
    let tripled = triples(my_number); // Tripling number. Now it is 30
    let same = does_nothing(my_number); // Sorry, it's the same: 10.
}

Паттерны изменчивости

Изменчивость внешняя Arc и внутренняя RefCell

interior-mutability

Внешняя изменчивость:

fn main(){
    let x = Arc::new(5);
    let y = x.clone();
// Счетчик ссылок Arc изменился без использования mut и переменная `x` неизменчивая 
// Внутренней изменчивости быть не может так как Arc создает `&T` 
}

Внутренняя изменчивость:

fn main(){
    let x = RefCell::new(5);
    let y = x.borrow_mut(); возвращает `&mut T`
// Refcell выполняет проверку заимствования во время выполнения и если создать еще одну `&mut T` будет panic! 
}

Внутренняя изменяемость (interior mutability) - это паттерн проектирования Rust, который позволяет вам изменять данные даже при наличии неизменяемых ссылок на эти данные; обычно такое действие запрещено правилами заимствования.

Мы можем использовать типы, в которых применяется паттерн внутренней изменяемости, только если мы можем гарантировать, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что компилятор не сможет этого гарантировать. В этом случае небезопасный код оборачивается безопасным API, и внешне тип остаётся неизменяемым.

Если вы нарушите правила заимствования, работая с ссылками, то будет ошибка компиляции. Если вы работаете с RefCell< T> и нарушите эти правила, то программа вызовет панику и завершится. Т.е. RefCell проверяет правила заимствования во время выполнения. Преимущества проверки правил заимствования во время компиляции заключаются в том, что ошибки будут обнаруживаться раньше - ещё в процессе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее. Преимущество проверки правил заимствования во время выполнения заключается в том, что определённые сценарии, безопасные для памяти, разрешаются там, где они были бы запрещены проверкой во время компиляции.

Изменяемость mut (Внутренняя Внешняя )

Изменяемость — это свойство либо ссылки (&mut), либо имени (let mut)

тип изменяемости — внутренняя или внешняя — определяется самим типом.

Внутренняя (interior mutability) изменяемость изменяемый объект полностью находится внутри самой структуры. По этой причине, метод Arc::clone() возвращает неизменяемую ссылку (&T)

fn main(){
    use std::sync::Arc;

    let x = Arc::new(5);

    let y = x.clone();
    let z = x.clone();
    let w = x.clone();
}

Внешняя (exterior mutability) изменяемость

RefCell возвращает изменяемую ссылку &mut при помощи метода borrow_mut()

fn main(){
    use std::cell::RefCell;

    let x = RefCell::new(42);

    let y = x.borrow_mut();
    let z = x.borrow_mut();// ошибка
}

Типаж Copy изменяет поведения при перемещении

Можно использовать данные после перемещения только если они не ссылаются на данные в другое место

&T, i32, Array, &str поведение Copy ( копируются неявно при присваивании ==, т.е. создается новая копия или явно создать копию .clone())

&mut T, Vec, String, Box поведение Clone (т.е. не могут копироваться при присваивании ==, могут moving перемещаться или явно клонировался в новую копию)

Мутирующие реализации Clone

Можно изменить будущий клон

Clone Copy

pub trait Copy: Clone {} // если хочешь Copy то твой тип должен еще и Clone реализовать

pub trait Clone {
    [must_use = "клонирование часто обходится дорого и не вызывает побочных эффектов"]
    pub fn clone (& self) -> Self;

    pub fn clone_from (&mut self, source: & Self) {...}
}
fn main(){}

#![feature(negative_impls)]

#![feature(negative_impls)]
 
struct MyStruct;
impl !Clone for MyStruct {}
impl !Copy for MyStruct {}
 
fn main() {
    let m = MyStruct;
    let m2 = m; // moved
    let m3 = m; // ❌ Error `does not implement the `Copy` trait`
}

ToOwned

toowned

ToOwned — это более универсальная версия Clone. Clone позволяет нам взять &T и превратить его в T, но ToOwned позволяет нам взять &Borrowed и превратить его в Owned where Owned: Borrow< Borrowed>

Другими словами, мы не можем «клонировать» &str в String, или &Path в PathBuf, или &OsStr в OsString, поскольку сигнатура метода клонирования не поддерживает такое клонирование перекрестного типа, и это для чего был создан ToOwned. Очень редко нам когда-либо понадобится применять ее для любого из наших типов.

clone_from вместо clone чтобы избежать дополнительного выделение емкости

clone

clone не создает для клонированного vec такую же емкость как у оригинала, из-за этого клонированный Vec снова выделит емкость

clone_from является альтернативой clone a.clone_from(&b); эквивалентно a = b.clone(); но сразу создает емкость клонируемого объекта

fn main(){
   let mut v1: Vec<u32> = Vec::with_capacity(99);
   let v2: Vec<u32> = vec![1, 2, 3];
   v1.clone_from(&v2); // емкость v1 используется для v2
   assert_eq!(v1.capacity(), 99);
}

Можно попутать мутацию локальных данных с вашими за счет срабатывания Copy


#[derive(Debug)]
struct A{
    pos:Option
}

impl A{
    fn get_shared(&mut self)->Option{ // просто сработает Copy, отдаем по значению
        self.pos
    }
    fn get_muted(&mut self)->&mut Option{// возвращаем изменяемую ссылку
        &mut self.pos
    }
    fn set_pos(&mut self){
        if let Some(ref mut pos) = self.get_shared(){
            *pos = 2; // ❌  тут локальные данные 
        }
        assert_eq!(Some(1),self.pos);// ничего не поменялось, мы просто изменили данные локальные данные но self не поменялся
        if let Some(ref mut pos) = self.get_muted(){
            *pos = 2;
        }
        assert_eq!(Some(2),self.pos); // да изменили состояние
    }
}

fn main() {
    let mut a = A{pos:Some(1)};
    a.set_pos();
}

По умолчанию пользовательские типы не реализуют Copy даже если состоят только из Copy типов т.е. требуется явно реализовать Copy

// pub trait Copy: Clone {...

Хочешь быть Copy то реализуй еще и Clone

И не должно быть реализации Drop

Если тип Copy то его Clone реализация должна только вернуть *self

//#[derive(Clone,Copy,Debug)] через derive будет добавлено ограничение копирования по параметрам типа, что не всегда желательно.
#[derive(Debug)]
struct WithCopy(i32);

impl Copy for WithCopy{}
impl Clone for WithCopy{
     fn clone(&self) -> Self {
        *self
     }
}
fn main(){}

Хочешь быть только Clone. Будет семантика moving

#[derive(Debug)]
struct WithoutCopy(i32);

impl Clone for WithoutCopy{
     fn clone(&self) -> Self {
        WithoutCopy(self.0)
     }
}

fn main() { 
    let a = WithCopy(1);
    let b = a;// Copy
    println!("{:?} {:?}",a,b);
    
    let a = WithoutCopy(1);
    let b = a;// moving semantic (Перемещение указателя на данные в куче, данные остали на том же адресе)
    //println!("{:?}",a);// Error
    println!("{:?}",b);

    let c = b.clone();
    println!("{:?} {:?}",c,b);  
}

В чем разница между Copy и Clone?

moves-copies-and-clones-in-rust

Copy типы не владеют ресурсами в куче, поэтому могут спокойно побитово копироваться неявно при использовании "=", а типы 'не Copy' !Copy которые владеют ресурсами в куче как Vec< T> не могут копироваться, они перемещаются.

В общем, любой реализующий тип Drop не может быть реализован Copy. Потому что Drop он реализуется типами, которые владеют некоторым ресурсом и, следовательно, не могут быть просто побитовое копирование. Но Copy типы должны быть легко копируемыми. Следовательно, Drop и Copy не смешивайте.

Клонирование - это явное действие x.clone(). Реализация Clone может обеспечивать любое зависящее от типа поведение, необходимое для безопасного дублирования значений. Например, реализация Clone String должна скопировать указанный строковый буфер в куче. Простая побитовая копия String значений просто скопирует указатель, что приведет к двойному освобождению строки. По этой причине String Clone но нет !Copy.

&T и raw pointers являются Copy так как они не владеют данными на которые указывают и для их копии требуется только копия ссылки а для копии Box< T> требуется и копия данных, а это небезопасно

&mut T не Copy так как владеет данными и не может быть две мутирующие ссылки

Обобщая последний случай, реализация любого типа Drop невозможна Copy, потому что он управляет некоторым ресурсом, помимо своих собственных size_of::< T> байтов.

Clone нужен для глубокого копирования, но для типов Rc и Arc приращение счетчика ссылок вместо глубокого копирования.

Так работает перемещение (так как нет Copy) для типов владеющих данными в куче

Так работает Clone клонирования для типов с ресурсами в куче. Т.е. создает клон с новыми данными в куче

moves-copies-and-clones-in-rust

derive работает не всегда, Clone делегирует имплементацию к Copy так как Copy наследуется от Clone

//#[derive(Clone, Copy)] // Wrapper<Vec<i32>> не будет : Copy , хотя * const Vec< i32>: Copy // Должны сами реализовать
struct Wrapper<T> {
    ptr: *const T,
}
impl<T> Copy for Wrapper<T> {}

impl<T> Clone for Wrapper<T> {
   fn clone(&self) -> Wrapper<T> {
       *self // делегируем к `Copy`
   }
}
fn main(){}

без Copy не работает

// без Copy не работает
#[derive(Clone,Copy)]
pub struct Unit(pub i32);

fn foo(value:&Unit) {
    let value = value.clone();
    
     let param1 = Some(1);
     param1.and_then(move |_v|{
            let param = Some(1);
            param.and_then(move |_v|{
               ex(move|&value|{1},&value);
               Some(1)
            }).and_then(move |_v|{
               let _p:Unit = value;
                Some(1)
            });
        Some(1)
     }); 
} 

pub fn ex( f: fn(&Unit) -> i32,arg:&Unit)  
{
 //f(arg);
}

fn main() {
    let unit = Unit(1);
    foo(&unit); 
}

Но даже если это объекты Copy но их много и происходит постоянное копирование из CPY то лучше использовать ссылку

Агрегаты из Copy объектов тоже Copy:

#[derive(Clone, Copy)]
struct Point { x: f64, y: f64 }

(Point, Point)

[Point; 1024] // копировать можно, но не стоит

Box<T> и Vec<T> не Copy — должны освобождать память

Основная цель владения — управление данными кучи. Отслеживание того, какие части кода используют какие данные, минимизация количества дублирующихся данных и очистка неиспользуемых данных в куче, чтобы не исчерпать пространство, — все эти проблемы решает владение.

В стеке должна быть одна ссылка на кучу поэтому при передаче владения данными передающая ссылка не используется при передаче владения передающий уже не может быть использован.

На новую переменную в стеке копируются данные (перемещаются) и переменная становится их владельцем

Владение — это то, как Rust достигает своей главной цели — безопасности памяти и скорости.

  • владение
  • заимствование, и связанная с ним возможность «ссылки»
  • время жизни, расширение понятия заимствования

Создание объекта определенного типа и связывание (binding) его с именем переменной создаёт ресурс в памяти, который будет валидироваться на протяжении всего его времени жизни (lifetime). Такую переменную называют владельцем (owner) ресурса.

Управление ресурсами на основе области видимости Rust использует последнее место использования ресурса или конец области видимости функции в качестве места, где свою работу выполняет деструктор и освобождает ранее выделенные ресурсы. Это может быть вам знакомо как идиома RAII (Resource Acquisition Is Initialization) из С++

В Rust по умолчанию вообще нет сборщика мусора. Память управляется через систему владения (ownership) и RAII Компилятор вставляет вызовы освобождения в нужные места на этапе компиляции.

Иерархическое удаление (Dropping is Hierarchical) Когда структура drop'ается, в начале происходит drop самой структуры, а потом её детей по очереди и так далее. Ресурсы должны быть drop'нуты только один раз!

Передача владения (Moving Ownership) Когда владелец ресурса используется в качестве аргумента функции, владение передаётся параметру этой функции. После передачи (move) переменной из оригинальной функции, перемещённую переменную больше нельзя использовать в месте откуда мы её передали.

Четыре общие стратегии могут помочь с проблемами владения:

Правила владения:

  • У каждого значения в Rust есть владелец,
  • У значения может быть только один владелец в один момент времени,
  • Когда владелец покидает область видимости, значение удаляется.

Четыре общие стратегии могут помочь с проблемами владения:

  1. Используйте ссылки, если полное владение не требуется ( &)
  2. Дублируйте значение (Clone)
  3. Рефакторинг кода для уменьшения количества долгоживущих объектов
  4. Оберните свои данные в тип, предназначенный для решения проблем с владения (Rc, Arc)

Система владения ресурсами — это яркий пример абстракции с нулевой стоимостью.

стоимость абстракций должна быть настолько малой, насколько это возможно без ущерба для работоспособности

Причина в проблеме безопасности

При перемещении вектора (передача владения) стековые данные копируются и теперь уже два указателя ссылаются на данные в куче (способствует гонке за данными)

Поэтому запрещается использовать уже перемещенный вектор

Данные копируемые из стека не ссылаются на другое место следовательно

fn main(){
    let mut i = 5;
    //let mut i2 = &i; // а так будет заимствование ссылки из стека и `i` ужэ нельзя менять
    let mut i2 = i;
    
   println!("{}",i);
   println!("{}",i2);

    let arr = [1,2,3];
    let arr2 = arr;
    print!("{}",arr[0]);

    let c = (1,"hi",'s');// кортеж
    let c2 = c;
    print!("{}",c.0);
}

Rust гарантирует, что существует ровно одно связывание какого-либо ресурса (то что Clone или содержит данные в куче)

Вектор это ресурс

Вектора всегда размещают данные в куче.

Сам вектор (т.е. некоторые данные) создается в стеке

В итоге стековые данные содержат указатель на данные в куче !

// №1

fn main(){
    let v = vec![1, 2, 3];
    let v2 = v;// v2 принимает владение  (создается копия данных стека)
    print!("{}",v[0]);//  «use of moved value» («используется перемещённое значение»)
}

// №2

fn main(){
    let v3 = vec![10,20,30];
    fn take(v:Vec<i32>){ }
    take(v3);// take принимает владение (создается копия данных стека)
    // и при выходе из области видимости завладевшие данные будут удалены
    print!("{}",v3[0]);//  «use of moved value» («используется перемещённое значение»)
}

вернуть владение из ф-ции (Returning Ownership)

Можно вернуть переданный аргумент обратно

fn foo(v:Vec<i32>) -> Vec<i32> { v } 

// Но лучьше передавать в ф-цию данные по ссылке это наз. 
// Заимствование и данные не удалятся
fn foo2(v:&mut Vec<i32>) {  } 

fn main(){
   foo(vec![1]);
   let mut v = vec![1];
   foo2(&mut v);
}

перемещение (владение)

Перемещение происходит если тип не реализует Copy т.е. не может копироваться на стеке, то эму остается только полностью переместится в новую переменную, даже при наличии реализации Clone, при Clone можно создать явно копию

fn main(){
    struct R;
    let r = R;
    let r2 = r;// r перемещена в r2 т.е. r2 завладела r
    // тут `r` уже удалена и обращение к ней вызовет ошибку на этапе компиляции
}

Копирование будет если есть реализация Copy и Clone

#[derive(Debug,Copy,Clone)]
struct R;

fn main(){
    let r = R;
    let r2 = r;// r скопирована в r2
    println!("{:?} {:?}",r,r2);
}

При присваивании (let x = y) или при передаче функции аргумента по значению (foo(x)), владение ресурсами передаётся. В языке Rust это называется перемещением. После перемещения ресурсов, переменная, владевшая ресурсами ранее, не может быть использована. Это предотвращает создание висячих указателей.

fn main(){
   // Вектор не может Copy, он может переместится 
    let mut a = vec![1,1];
    a[0] = 9;
    let mut b = a;// у вектора это перемещение, 'а' ужэ использовать нельзя

   // Массив может быть Copy (так как он на стеке)
    let mut a = [1];
    a[0] = 9;
    let mut b = a;// обычное копирование
    a[0]=8;
    b[0]=11;
    println!("b={}",b[0]);//11
    println!("a={}",a[0]);//8

    let  z = &a;
    println!("a={}",a[0]);//8
    a[0]=8;// ошибка, изменять 'a' уже нельзя на него есть ссылка

    let mut  z = &mut a;// перемещение, 'a' ужэ не валидна
    print!("a={}",a);// ошибка, использовать уже тоже нельзя
    a[0]=8;// ошибка, изменять 'a' уже нельзя, на него есть ссылка
}

Копирование при отсутствии указателя на ресурсы и перемещение когда есть указатель на ресурсы (кучу или стек)

// Эта функция берёт во владение память, выделенную в куче
fn destroy_box(c: Box<i32>) {
    println!("Уничтожаем упаковку, в которой хранится {}", c);
    // `c` уничтожится, и память будет освобождена
}
fn main(){
    // Целое число выделенное в стеке
    let x = 5u32;
    // Копируем `x` в `y`. В данном случае нет ресурсов для перемещения
    let y = x;
    // Оба значения можно использовать независимо
    println!("x равен {}, а y равен {}", x, y);

    // `a` - указатель на целое число, выделенное в куче
    let a = Box::new(5i32);
    println!("a содержит: {}", a);

    // Перемещаем `a` в `b`
    let b = a;
    // Адрес указателя `a` перемешается (но не данные) в `b`.
    // Оба указателя указывают на одни и те же данные в куче, но
    // `b` теперь владеет ими.

    // Ошибка! `a` больше не может получить доступ к данным, потому что
    // больше не владеет данными в куче.
    println!("a содержит: {}", a);
    // Эта функция берет во владение память, выделенную в куче, которой ранее владела `b`
    destroy_box(b);
    // Поскольку в данный момент память в куче уже освобождена, это действие
    // приведёт к разыменованию освобождённой памяти, но это запрещено компилятором
    // Ошибка! Причина та же, что и в прошлый раз
    println!("b содержит: {}", b);
}

Копирование при отсутствии указателя на ресурсы и перемещение когда есть указатель на ресурсы (кучу или стек).

Простые типы i32, Array, &str реализуют типаж Copy

fn main(){
    let arr = [1,2,3];
    let mut a =arr;
    a[0]=99;
    print!("{:?}{:?}",a,arr);//[99, 2, 3][1, 2, 3] ошибки нет arr скопирован

    let arr =vec![1,2,3];
    let a =arr;
    print!("{:?}",arr);// ошибка arr перемещен и ссылка на ресурс удалена у него

    let s="ss";
    let mut c =s;
    c="cc";
    println!("{} and {}", c,s);// cc and ss

    let mut s="ss";
    let mut c =&mut s;
    *c="cc";
    println!("{} ", c);
    println!("{}", s);// ошибка ссылка на ресур перемещена в c
}

ссылка не забирает владение ресурсом, а только заимствует на время нахождения в области видимости.

А вот умные указатели завладевают данными типа String,Vec,Rc,Arc,...

Гонки данных

Гонка данных возникает, когда два разных потока обращаются к одному и тому же участку памяти при следующих условиях:

  • По крайней мере один из них — write.
  • Механизма синхронизации, обеспечивающего упорядочивание доступа, не существует.

В Rust позволяет иметь только одну изменяемую ссылку на объект или несколько неизменяемых, но не оба типа ссылок одновременно.

Правило ссылок предотвращает гонки данных. Что такое гонки данных? Это когда чтение переменной одновременно происходит с записью в эту переменную, из-за чего возможно Undefined Behaviour (UB). Такое часто происходит в многопоточных программах.

Гонки данных проблема Недействительный итератор

Недействительный итератор предостерегает от недействительного итератора когда в момент его итерации мы его меняем

fn main(){
    // Мы не можем изменить v, потому что он уже заимствован в цикле
    let mut v = vec![1, 2, 3];
    // Когда мы обходим вектор, мы получаем лишь ссылки на элементы.
    for i in & v {
        println!("{}", i);
        v.push(34);// Мы не можем изменить v, потому что он уже заимствован в цикле.
    }
}

fn main(){
    let mut arr =[1,2,3];
    for i in  &arr {
        arr[0] = 0;
        println!("{}", i);
    }
}

Гонки данных проблема Использование после освобождения

Использование после освобождения (use after free)

Ссылки не должны жить дольше, чем ресурс, на который они ссылаются

fn main(){
    let y: &i32;// создали пустую ссылку
    {
        let x = 5;
        y = &x;// y в глобальной области получает ссылку на локальный x
        // ошибка при заимствовании локальной ссылки к глобальной
    }
    //println!("{}", y);
}

ресурсы в одном блоке освобождаются в порядке, противоположном порядку их объявления: x удалится первым и y будет ссылаться (жить дольше чем данные на которые он ссылался)

fn main(){
    let y: &i32;
    let x = 5;
    y = &x;
    println!("{}", y);

    // так не будет ошибки (местами инициализацию поменяли)
    // сначало удалится y
    let x = 5;
    let y: &i32;
    y = &x;
    println!("{}", y);
}

Висячие указатели, dangling pointers

Ссылка не должна жить дольше, чем существует её владелец, который может быть удален по выходу из области видимости. Правило для ссылок предотвращает некорректное их использование, когда она ссылается на несуществующие данные

&'_ mut T 

(shared) &'_ T  это  Copy
fn main(){
// &'_ mut T не может быть Copy так как мы получим две &mut ссылки. Но &'_ T всегда Copy

// &'_ T => Copy
    let s:String = String::from("...");
    let ls:&String = &s;
    println!("For &:{s} {ls}");// тут имеим доступ и к данным на прямую и через ссылку одновременно 

// &'_ mut T => заимствует ресурс на условиях уникального доступа
    let mut s:String = String::from("...");
    // `&'_ mut T` не реализует Copy
    // мы не можем получить доступ к данным напрямую пока живет `mut T`
    let ls:&mut String = &mut s;
    println!("For &mut:{ls}");  
    // ... тут срабатывает Drop для ls, поэтому можем обратиться к данным напрямую
    println!("For &mut:{s}");
}

Перезаимствование

reborrowing для &mut

в чем смысл?

Reborrowing для &mut (для shared ссылок нет reborrowing так как они просто копируются. &'_ T всегда Copy, а &'_ mut T нет Copy)

Автоматическая замена &r на &mut *r или &*r

fn do_nothing(xs: &mut [i32]) {
    xs
...
}
 
// На самом деле компилятор всегда разворачивает ссылку в данные и берет на них ссылку
fn do_nothing(xs: &mut [i32]) {
    {
      let tmp = &mut *xs; // временная ссылка с коротким вж
      ...
    }
}

Non Lexical Lifetimes

Неявные оптимизации правил заимствования ссылок

Нарушение правил работы со ссылками, при одновременном существовании мутабельной &mut num и не мутабельной ссылки &mut

struct NumMutFef<'a>{
    num: &'a mut i32
}
 
fn main(){
    let mut num = 3;
    let num_mut_ref = NumMutFef{
        num: &mut num
    };
    println!("{}", &num);// ✅ OK
    // Но как такое может быть, если ссылка `&mut num` в NumMutFef живет до конца функции main и использование shared ссылки `&num` в `println!("{}", &num);` использует ее повторно! По идее компилятор должен запретить использовать нам shared ссылку, но так и будет если мы явно реализуем трейт Drop, что затрет оптимизацию компилятора с сокращенным временем жизни мутабельной ссылки до вызова shared ссылки.  
}

Теперь мы реализуем Drop, что расширит время жизни мутабельной ссылки, как это и должно быть до конца функции main

struct NumMutFef<'a>{
    num: &'a mut i32
}
impl Drop for NumMutFef<'_> {
    fn drop(&mut self) {}
}
fn main(){
    let mut num = 3;
    let num_mut_ref = NumMutFef{
        num: &mut num
    };
    println!("{}", &num);// ❌ ERROR
}

Оптимизация с сокращенным временем жизни, выглядит так:

struct NumMutFef<'a>{
    num: &'a mut i32
}
impl Drop for NumMutFef<'_> {
    fn drop(&mut self) {}
}
fn main(){
    let mut num = 3;
    {
        let num_mut_ref = NumMutFef{
            num: &mut num
        };
    }
    println!("{}", &num);// ✅ OK
}

Или явно вызвать drop, что явно сократит время жизни и компилятор разрешит использовать shared ссылку после drop

struct NumMutFef<'a>{
    num: &'a mut i32
}
impl Drop for NumMutFef<'_> {
    fn drop(&mut self) {
         
    }
}
fn main(){
    let mut num = 3;

    let num_mut_ref = NumMutFef{
        num: &mut num
    };
    drop(num_mut_ref);
    println!("{}", &num);
}

Сравнение ссылок

Подобно оператору .

Операторы сравнения в Rust «видят сквозь» любое число ссылок при условии, что типы обоих операндов одинаковы

fn main(){
    let x = 10;
    let y = 10;
    let rx = &x;
    let ry = &y;
    let rrx = &rx;
    let rry = &ry;
    assert!(rrx <= rry);
    assert!(rrx == rry);
// Последнее утверждение выполняется, несмотря на то что rrx и rry указывают на разные значения ( rx и ry ), т. к. оператор == проследует по всем ссылкам и сравнивает целевые объекты в конце цепочек, x и y.

// Если все же нужно знать, указывают ли две ссылки на один и тот же адрес в памяти, то можно воспользоваться функцией std::ptr::eq, которая сравнивает ссылки как адреса:

    assert!(rx == ry);// объекты ссылки равны
    assert!(!std::ptr::eq(rx, ry)); // но расположены по разным адресам
}

ссылка на ссылку

fn modify_reference(value: &mut &mut i32) {
    **value += 1; 
}

fn main() {
    let mut x = 10;
    let mut_ref = &mut x; // Изменяемая ссылка на x.
    let mut ref_to_mut_ref = mut_ref; // Изменяемая ссылка на изменяемую ссылку.

    modify_reference(&mut ref_to_mut_ref); // Передаём изменяемую ссылку на изменяемую ссылку.

    println!("x: {}", x); // Вывод: x: 11
} 
 
// Ссылки на ссылки (&mut &mut T или &&T) появляются, когда есть специфические ситуации, требующие дополнительного уровня ссылок.

круговые указатели, ссылочный цикл на примере дерева

reference-cycles

Rust допускает утечку памяти, используя Rc< T> и RefCell< T>: можно создавать циклические ссылки, где элементы ссылаются друг на друга замыкая круг и в итоге никогда не удаляются.

Это создает утечки памяти, потому что счетчик ссылок каждого элемента в цикле никогда не достигнет 0, и значения никогда не будут сброшены.

Для предотвращения круговых указателей используют Weak

use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell< Weak< Node>>,
    children: RefCell< Vec< Rc< Node>>>,
}

fn main() {
  // Отсутствие бесконечного вывода указывает на то, что этот код не создавал ссылочный цикл.
 let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

ref/ref mut не дает при деструкции завладеть данными т.е. вместо перемещения используется заимствование

В отличии от & , ref не обозначает нечто с чем мы производим сопоставление.

По умолчанию без ref они перемещаются в ветвь match При наличии ref, они заимствуются и представляются в качестве ссылок

& обозначает что паттерн ожидает ссылку на объект. Таким образом & является частью паттерна. Отсюда &Foo будет сопоставляться иначе чем Foo

ref отмечает что вы хотите получить ссылку на распакованное значение. Оно не участвует непосредственно в сопоставлении паттерна. Поэтому Foo(ref foo) будет сопоставляться тому же объекту, что и Foo(foo)

habr.com/ru/post/306582

При сопоставлении с образом match, let if, for происходит деструкция

fn main(){
 let maybe_name = Some(String::from("Alice"));
 
 match maybe_name { 
    Some(ref n) => println!("Hello, {}", n),
    _ => {},
 }
 // мы имеем доступ к maybe_name так как match не завладела данными при деструкции из-за ref
 println!("{:?} ", maybe_name);
}

Не перемещаем (moving) String, а берем на него ссылку

fn main(){
 let query_params: Vec<(String, String)> = vec![("key".to_string(),"value".to_string())];
 for &(ref name,ref  value) in &query_params { // без ref тип должен быть Copy
    println!("{:?}={:?}", name, value);
 }
}

ref для получения ссылки на поле структуры или кортежа.

#[derive(Clone, Copy)]
struct Point { x: i32, y: i32 }

fn main() {
    let c = 'Q';

    // Заимствование с `ref` по левую сторону от присваивания, эквивалетно
    // заимствованию с `&` по правую сторону.
    let ref ref_c1 = c;
    let ref_c2 = &c;

    println!("ref_c1 равно ref_c2: {}", *ref_c1 == *ref_c2);

    let point = Point { x: 0, y: 0 };

    // `ref` также может использоваться при деструктуризации структур.
    let _copy_of_x = {
        // `ref_to_x` - ссылка на поле `x` в `point`.
        let Point { x: ref ref_to_x, y: _ } = point;

        // Возвращаем копию поля `x` из `point`.
        *ref_to_x
    };

    // Изменяемая копия `point`
    let mut mutable_point = point;

    {
        // `ref` может использоваться вместе с `mut` для получения изменяемой ссылки.
        let Point { x: _, y: ref mut mut_ref_to_y } = mutable_point;

        // Изменяем поле `y` переменной `mutable_point` через изменяемую ссылку.
        *mut_ref_to_y = 1;
    }

    println!("point ({}, {})", point.x, point.y);
    println!("mutable_point ({}, {})", mutable_point.x, mutable_point.y);

    // Изменяемый кортеж с указателем
    let mut mutable_tuple = (Box::new(5u32), 3u32);
    
    {
        // Деструктурируем `mutable_tuple` чтобы изменить значение `last`.
        let (_, ref mut last) = mutable_tuple;
        *last = 2u32;
    }
    
    println!("tuple {:?}", mutable_tuple);
}

Для указателей необходимо различать деструктуризацию и разыменование, поскольку это разные концепции, которые используются иначе, чем в языке С.

Разыменование использует *

Деструктуризация использует &, ref и ref mut

fn main(){
    // Присваиваем ссылку на тип `i32`.
    // Символ `&` означает, что присваивается ссылка.
    let reference = &4;

    match reference {
        // Если `reference` - это шаблон, который сопоставляется с `&val`,
        // то это приведёт к сравнению:
        // `&i32`
        // `&val`
        // Мы видим, что если отбросить сопоставляемые `&`,то переменной `val` должно быть присвоено `i32`.
        &val => println!("Получаем значение через деструктуризацию: {:?}", val),
    }

    // Чтобы избежать символа `&`, нужно разыменовывать ссылку до сопоставления.
    match *reference {
        val => println!("Получаем значение через разыменование: {:?}", val),
    }

    // Что если у нас нет ссылки? `reference` была с `&`,
    // потому что правая часть была ссылкой. Но это не ссылка, потому что правая часть ею не является.
    let _not_a_reference = 3;

    // Rust предоставляет ключевое слово `ref` именно для этой цели.
    // Оно изменяет присваивание так, что создаётся ссылка для элемента.
 
    // Соответственно, для определения двух значений без ссылок, ссылки можно назначить с помощью `ref` и `ref mut`.
    let value = 5;
    let mut mut_value = 6;

    // Используйте ключевое слово `ref` для создания ссылки.
    match value {
        ref r => println!("Получили ссылку на значение: {:?}", r),
    }

    // Используйте `ref mut` аналогичным образом.
    match mut_value {
        ref mut m => {
            // Получаем ссылку. Её нужно разыменовать,
            // прежде чем мы сможем что-то добавить.
            *m += 10;
            println!("Мы добавили 10. `mut_value`: {:?}", m);
        },
    }
}

Заимствование - обращаться к данным без получения владения над ними. Для этого Rust предоставляет механизм заимствования Вместо передачи объектов по значению (T), объекты могут быть переданы по ссылке (&T, &mut T) и при выходе из области видимости не удаляет данные.

гонки данных Undefined Behaviour (UB)

В Rust позволяет иметь только одну изменяемую ссылку на объект или несколько неизменяемых, но не оба типа ссылок одновременно.

Правило ссылок предотвращает гонки данных. Что такое гонки данных? Это когда чтение переменной одновременно происходит с записью в эту переменную, из-за чего возможно Undefined Behaviour (UB). Такое часто происходит в многопоточных программах.

Имена, которые заимствуют что-то, не освобождают ресурс, когда они выходят из области видимости.

❌ Ошибка use of moved value: v

fn main(){
    let v = vec![10,20,30];
    fn foo1(v:Vec<i32>){}
    foo1(v);// после выхода из ф-ции завладевшие данные будут удалены
    println!("v = {}", v[0]);// Ошибка использование перемещенными данными
}

Решение (не идиоматичный). Если бы нам нужно было вернуть владение обратно из функции

fn main(){
    let v = vec![10,20,30];
    fn foo2(v:Vec<i32>) -> Vec<i32> {
        // делаем что-либо с v
        // возвращаем владение
        v
    }

    let v2 = foo2(v);
    println!("v = {}", v2[0]);
}

Решение

Но этот способ усложняет работу. Заимствование , передача по ссылки, решает это

fn main(){
    let v = vec![10,20,30];
    // аргумент по ссылке
    fn foo3(v:&Vec<i32>){
        println!("foo3 v = {}", v[0]);
    }
    foo3(&v);// и передача по ссылке, данные из стека заимствуются и не будут удалены из стека
    println!("global v = {}", v[0]);
}

область заимствования

fn main(){
    let mut i = 5;
    {
            let i2 = &mut i;
            //println!("{}",i); ошибка обращение к временно перемещенным данным ими владеет i2
            *i2=9; // операция * - разименовывание (Dereferencing)
            println!("{}",i2);//9  ошибки нет
    }
    
    println!("{}",i);// ошибки нет так как было заимствование и при выходе из области видимости i2 не удалило данные на которые ссылалась( i)
    let i2 = &mut i;
    println!("{}",i);// ошибка есть , в этой области видимости данными `i` уже владеет `i2` и `i` использовать нельзя
}

Мутабельная ссылка (Mutable Borrow)

Почему правило заимствования позволяет иметь только одну мутабельную ссылку или множество немутабельных Из-за возможной некорректности программы в процессе выполнения и компилятор лучше оптимизирует код зная что мутабельная ссылка одна

fn main(){
    let mut a = 2;
    let mut b = 0;
    foo(&mut a,&mut b);
    assert_eq!(10,b);
   
// Но если бы можно было иметь больше одной мутабельной ссылки
// то мы бы могли передать мутабельную ссылку на одни и те же данные и в итоге меняли их но с разных ссылок
    foo(&mut a,&mut a);
    assert_eq!(100,b);
}
  
fn foo(a:&mut i32,b:&mut i32){
    if *a > 1 {
        *b = 10;
    }
    if *a > 5 {
        *b = 100;
    }
}

Изменяемость mut при перемешении


fn main() {
    let immutable_box = Box::new(5u32);
    println!("immutable_box содержит в себе {}", immutable_box);

    //*immutable_box = 4;// Ошибка изменяемости

    // Переместить упаковку, изменив её владение (и изменяемость)
    let mut mutable_box = immutable_box;

    println!("mutable_box содержит в себе {}", mutable_box);

    // Изменяем данные внутри упаковки
    *mutable_box = 4;
    println!("mutable_box now содержит в себе {}", mutable_box);
}

Изменяемость mut при перемешении

#[allow(dead_code)]
#[derive(Clone, Copy)]
struct Book {
    // `&'static str` является ссылкой на строку, выделенную в постоянном запоминающем устройстве
    author: &'static str,
    year: u32,
}
fn borrow_book(book: &Book) {println!("{}",book.year);}
fn new_edition(book: &mut Book) {  book.year = 2014; println!("{}",book.year);}

fn main() {
    let immutabook = Book {
        author: "Douglas Hofstadter", // string literals have type `&'static str`
        year: 1979,
    };
   
    let mut mutabook = immutabook; // тут сработал Copy так как все данные тоже Copy
   
    borrow_book(&immutabook);
    borrow_book(&mutabook);
    new_edition(&mut mutabook);
}

Типаж AsRef является преобразующим типажом. Он используется в обобщённом коде для преобразования некоторого значения в ссылку на внутренние данные структуры. Это очень важная черта, широко используемая в библиотеке std и помогающая с плавным преобразованием ссылок из одного типа в другой.

Основное различие между двумя чертами заключается в намерении:

  • Используйте, AsRef когда целью является возможность преобразования в ссылку &T/&mut T
  • Используйте, Borrow когда цель абстрагироваться, владеем значением или ссылаться ссылкой, нам это неважно

From и Into представляют преобразования, которые принимают значение одного типа и возвращают значение другого. Принимая во внимание, что черты AsRef и AsMut заимствуют ссылку одного типа у другого, From берут Into на себя ответственность за свой аргумент, преобразуют его, а затем возвращают право собственности на результат обратно вызывающему объекту.

Другое отличие состоит в том, что преобразования AsRef(и AsMut) должны быть дешевыми, т.е. они не требуют копирования данных или выделения новой памяти и в большинстве случаев выполняются за постоянное время O(1), тогда как From преобразования Into не гарантированно будут дешевыми. Это не часть их контракта.

Выберите, Borrow когда вы хотите абстрагироваться от различных видов заимствований или когда вы строите структуру данных, которая обрабатывает собственные и заимствованные значения эквивалентными способами, такими как хеширование и сравнение.

Выберите, AsRef когда вы хотите преобразовать что-либо в ссылку напрямую, и вы пишете общий код.

// подходит для `&mut T` и `&T` и `T` и `mut T`
fn generic_as_ref_shared<T: AsRef<str>>(value: T) {
    println!("{:?}", value.as_ref()); 
}

fn main() {
    let s = String::from("Привет");

    generic_as_ref_shared("Привет");    // &str
    generic_as_ref_shared(&"Привет");   // &&str
    generic_as_ref_shared(&s);          // &String
    generic_as_ref_shared(&mut s.clone()); // &mut String
    generic_as_ref_shared(s);           // String
}

impl AsRef< [u8]> for String

fn main(){
   let string: String = "some text".into();
   let bytes: &[u8] = string.as_ref();
}

Функция принимает Vec или []

fn batch<Matrix: AsRef<[Row]>, Row: AsRef<[f32]>>(features: Matrix) {
    for row in features.as_ref() {
        for cell in row.as_ref() {
            print!("{} ", cell);
        }
        println!("");
    }
}
fn main(){
  let v = vec![vec![1f32],vec![2f32]];
  batch(v);

  let v = [[1f32],[2f32]];
  batch(v);
}
use derive_more::{AsRef, AsMut};

pub fn just_print_stringy<S: AsRef<String>>(v: S) {
    println!("{}", v.as_ref())
}

pub fn add_hi<S: AsMut<String>>(mut v: S) {
    v.as_mut().push_str(" Hi")
}

#[derive(AsMut, AsRef)]
pub struct Nickname(String);

impl Nickname {
    pub fn new<S: Into<String>>(nickname: S) -> Self {
        Self(nickname.into())
    }
}
fn main(){
   let mut nickname = Nickname::new("Vasya");
   add_hi(&mut nickname);
   just_print_stringy(&nickname);
}

ф-ция принимает String или &str (пример для generic)

fn take_a_str(some: impl AsRef< str>) { // тоже самое `fn take_a_str< T:AsRef< str>>(some: T) {...`
    let some = some.as_ref();
    println!("{some}");
}
use core::fmt::Debug;
fn take_a_str_into(some: impl Into< String>+Debug) {
    println!("{:?}",some);
}
fn main() {
    take_a_str("str");
    take_a_str("String".to_string());
    
    // also `&String` is supported:
    let string_ref = "StringRef".to_string();
    take_a_str(&string_ref);

    take_a_str_into("str");
    take_a_str_into("String".to_string());
    let string_ref = "StringRef".to_string();
    take_a_str_into(&string_ref);
}

ф-ция возвращает String или &str

Удалить пробелы. Если входные данные будут мутироваться, то вернем String иначе возвращать входную строку &str. Т.е. клонирование при мутации Cow, отложите выделение памяти как можно на дольше.

use std::borrow::Cow;
// вариант с AsRef т.е. можно String присылать
fn remove_spaces<'a>(input: &'a(impl AsRef<str> + ?Sized)) -> Cow<'a, str> { // <'a> что бы можно было вернуть  Cow<'a...
    let input = input.as_ref();
    if input.contains(' ') {
        input
        .chars()
        .filter(|&x| x != ' ')
        .collect::<std::string::String>()
        .into()
    } else {
        // input.into() // Into<Cow<'a, str>> 
        // или полный синтаксис
        Into::<Cow<'_, str>>::into(input)  
    }
} 
fn main(){
    let s = remove_spaces("Herman"); // Cow::Borrowed  
    let len = s.len(); // impl Deref
    let owned: String = s.into_owned(); // memory is allocated for a new string

    let binding = "Herman Radtke".to_string();
    let s = remove_spaces(&binding); // Cow::Owned 
    let len = s.len(); // impl Deref
    let owned: String = s.into_owned(); // no new memory allocated as we already had a String
}

Структура преобразуется в ссылку &T нужного типа

#[derive(Debug,Clone)]
struct Wrap(String);

impl AsRef<String> for Wrap {
    fn as_ref(&self) -> &String {
        &self.0
    }
}
impl AsRef<str> for Wrap {
    fn as_ref(&self) -> &str {
        self.0.as_str()
    }
}
impl AsMut<String> for Wrap {
    fn as_mut(&mut self) -> &mut String {
        &mut self.0
    }
}
impl AsRef<[u8]> for Wrap {
    fn as_ref(&self) -> &[u8] {
        self.0.as_bytes()
    }
}
fn use_string<T:AsRef<str>>(item:&T){
    assert_eq!("...",item.as_ref());  
}
fn main(){
    let mut w = Wrap(String::from("..."));
    let v:&String = w.as_ref();
    let s:&str = w.as_ref();
    let v:&mut String = w.as_mut();
    use_string(&w);
    //println!("{}",w.as_ref());// если б была только одна реализация AsRef
    println!("{}",<Wrap as AsRef<str>>::as_ref(&w));// из-за наличия двух реализаций, мы уточним конкретную

    let bytes: &[u8] = w.as_ref(); // вывод типа компилятором на основе сигнатуры принимаемого
    assert_eq!([46,46,46],bytes);
}

Структура преобразуется в ссылку &T нужного типа

обобщенная реализация требует точной реализации в приведенный тип

#[derive(Debug,Clone)]
struct Wrap< T>(T);

impl< T> AsRef< T> for Wrap< T> {
    fn as_ref(&self) -> &T {
        &self.0
    }
}
impl< T> AsMut< T> for Wrap< T> {
    fn as_mut(&mut self) -> &mut T {
        &mut self.0
    }
}
impl AsRef< str> for Wrap< String> {
    fn as_ref(&self) -> &str {
        &self.0
    }
} 
impl AsRef< [u8]> for Wrap< String> {
    fn as_ref(&self) -> &[u8] {
        self.0.as_bytes()
    }
}
fn use_string< T:AsRef< String>>(item:&T){
    assert_eq!("...",item.as_ref());  
}
fn main(){
    let mut w = Wrap::< String>(String::from("..."));
    let v:&String = w.as_ref();
    //let s:&str = w.as_ref();
    let v:&mut String = w.as_mut();
    use_string(&w);
    //println!("{}",w.as_ref());// если б была только одна реализация AsRef
    //println!("{}",< Wrap::< str> as AsRef< str>>::as_ref(&w));// из-за наличия двух реализаций, мы уточним конкретную

    let bytes: &[u8] = w.as_ref(); // вывод типа компилятором на основе сигнатуры принимаемого
    assert_eq!([46,46,46],bytes);
}
// Box< T> implements AsMut< T>:
fn add_one< T: AsMut< u64>>(num: &mut T) {
    *num.as_mut() += 1;
}
fn main(){
   let mut boxed_num = Box::new(0);
   add_one(&mut boxed_num);
   assert_eq!(*boxed_num, 1);
}

Мотивация этих черт заключается в том, что искажение типа - это огромная боль в шее, и для функции очень приятно просто волшебным образом понять это для вас. В частности, если функция ожидает путь к файлу, у нас есть все эти опции:

impl AsRef<Path> for Path
impl AsRef<Path> for OsStr
impl AsRef<Path> for OsString
impl AsRef<Path> for str
impl AsRef<Path> for String
impl AsRef<Path> for PathBuf

Все они «просто работают», так что вы можете:

std::fs::File::open("hello.txt") // &str
std::fs::File::open(format!("hello-{}.txt", num)) и т. д.  // String

До насильственного принуждения люди были очень расстроены из-за необходимости проходить везде foo.as_slice()

Хорошим примером, где мы могли бы реализовать, AsRef было бы, если бы мы представили новый тип Moderator, который просто обертывал a User и добавлял некоторые определенные привилегии модерации:

asref--asmut

struct User {
    name: String,
    age: u32,
}
// К сожалению, стандартная библиотека не может предоставить универсальную реализацию, которая избавит нас от этого шаблона
impl AsRef<User> for User {
    fn as_ref(&self) -> &User {
        self
    }
}
enum Privilege {
    BanUsers,
    EditPosts,
    DeletePosts,
}
// Хотя у модераторов есть некоторые особые привилегии, они всё равно остаются обычными пользователями и должны иметь возможность делать всё то же самое
struct Moderator {
    user: User,
    privileges: Vec<Privilege>
}
impl AsRef<Moderator> for Moderator {
    fn as_ref(&self) -> &Moderator {
        self
    }
}
impl AsRef<User> for Moderator {
    fn as_ref(&self) -> &User {
        &self.user
    }
}
// это должно быть доступно для пользователей и модераторов (которые также являются пользователями)
fn create_post<U: AsRef<User>>(u: U) {
    let user = u.as_ref();
    // etc
}
fn example(user: User, moderator: Moderator) {
    create_post(&user);
    create_post(&moderator); // ✅
}
fn main(){}

Пример Into< String> + AsRef< str> можем использовать как ссылку и как владеющим into


fn validate(email:&str)->bool{
    true
}
pub fn new + AsRef>(email: S) -> Option {
   if  validate(email.as_ref()) {
        Some(email.into())
   } else {
        None
   }
}
fn main() {
  new("email");
  new("email".to_string());
}

Пример использования String и &str в методах одновременно


use std::cmp::PartialEq; 
use std::convert::AsRef;

struct Foo< T>(T);
impl < T>Foo< T>{
    fn new(v: T)->Self{
        Self(v)
    }
}
// В методах структуры сохраняется тип T при первом вызове и остается для этого типа навсегда
// при создании и только он будет подставляться в момент вызова
// Для возможности использовать str или String следует 
// 1. Писать в аргументах impl AsRef< str>
// 2. Либо снова определять новый тип < V:AsRef< str>> не подвязанный на типе подвязанном при создании структуры
impl < T:AsRef< str> + PartialEq>Foo< T>{
    fn cmp_good(&self,v:impl AsRef< str>)->bool{
        v.as_ref() == self.0.as_ref()
    }
    fn cmp_good_2< V:AsRef< str> + PartialEq>(&self, v:V)->bool{
        v.as_ref() == self.0.as_ref()
    }
    fn cmp_static(a:&str, b: T)->bool{
        a == b.as_ref()
    }
}
// тут не сохраняется тип между вызовами поэтому можно вызвать str или String
fn ext_cmp< T: AsRef< str>>(s: T) {
   assert_eq!("hello", s.as_ref());
}
fn main() {
    let f:Foo< String> = Foo::new("hello".to_owned());
    f.cmp_good("hello");
    f.cmp_good_2("hello");
    Foo::cmp_static("hello","hello");
    f.cmp_good("hello".to_owned());
    f.cmp_good_2("hello".to_owned());
    Foo::cmp_static("hello","hello".to_owned());
   
    let s = "hello";
    ext_cmp(s);
    let s = "hello".to_string();
    ext_cmp(s);
}
-------------------------------------------------------------------------
//или более обобщенный вид
impl < T: PartialEq>Foo< T>{
    fn cmp_good< V:PartialEq+ ?Sized>(&self, v:impl AsRef< V>)->bool where T:AsRef< V>{
        v.as_ref() == self.0.as_ref()
    }
}
let f:Foo< String> = Foo::new("hello".to_owned());
f.cmp_good::< str>("hello");

Пример использования AsRef для любых типов


use std::cmp::PartialEq; 
use std::convert::AsRef;
use std::marker::PhantomData;

pub trait TWrapKey< Q: ?Sized>:AsRef< Q>{}
pub struct WrapKey< T: ?Sized>(pub T);
impl< T: ?Sized> AsRef< T> for WrapKey< T>{
    fn as_ref(&self)->&T{
        &self.0
    }
}
impl< T> TWrapKey< T> for WrapKey< T>{}

pub struct Foo< U,T>(T,PhantomData< U>);

impl < U,T:TWrapKey< U>>Foo< U,T>{
    pub fn new(key:T)->Self{
        Self(key,PhantomData)
    }
}
impl < U:PartialEq,T: TWrapKey< U>>Foo< U,T>{
    pub fn cmp< Q:PartialEq< U> + ?Sized>(&self,v:impl TWrapKey< Q>)->bool {
        v.as_ref() == self.0.as_ref()
    } 
}

fn ext_cmp< T: AsRef< str>>(s: T) {
   assert_eq!("hello", s.as_ref());
}
// Для возможности использовать любой тип реализующий AsRef и PartialEq используя тип T через ссылку &T в ф-ции cmp
fn main() {
    let f/*:Foo< i32,_>*/ = Foo::new(WrapKey(1));
    let wrap = WrapKey(1);
    assert!(f.cmp(wrap));
  
    let f/*:Foo< String,_>*/ = Foo::new(WrapKey("hello".to_owned()));
    let wrap = WrapKey("hello");
    assert!(f.cmp(wrap));
    let wrap = WrapKey("hello".to_owned());
    assert!(f.cmp(wrap));
}

Пример использования AsRef как оболочка для применения Deref

ЗАЧЕМ ТУТ Deref ?


use std::convert::AsRef;
use std::ops::Deref;
use std::fmt::Debug;
#[derive(Debug,Clone,Copy)]
struct Wrapi32(i32);
impl AsRef< i32> for Wrapi32 {
    fn as_ref(&self) -> &i32 {
        &self.0
    }
}
fn foo_gen_type_as_ref< T:Debug+Copy,R:Deref< Target = T>,W:AsRef< T>>(arg:W) {
    let v:&T = &*arg.as_ref();
    // или
    //let v = *Deref::deref(&arg.as_ref());
    println!("{:?}",v);
}

fn main(){
    foo_gen_type_as_ref::< i32,&i32,Wrapi32>(Wrapi32(5));  
}

Абстрагирование от типа ввода

Улучшение эргономики

Обратной стороной этой идиомы является то, что компилятор генерирует больше кода из-за мономорфизации, что потенциально приводит к раздуванию кода.

Способ исправить Оптимизация уменьшения раздувания кода

(Каноническое решение этой проблемы состоит в том, чтобы исключить внутренний метод, который содержит весь код за вычетом общих преобразований, и оставить внешний метод как оболочку.)

crate momo

from-str-to-cow

Элегантный API

pub fn just_print_stringy(v: &str) {
    println!("{}", v)
}
pub fn add_hi(v: &mut String) {
    v.push_str(" Hi")
}
pub struct Nickname(String);
impl Nickname {
    pub fn new(nickname: String) -> Self {
        Self(nickname)
    }
}
fn main(){}

Однако из-за необходимости явного преобразования типов в Rust такой API может быть недостаточно эргономичным

fn main(){
   let mut nickname = Nickname::new("Vasya".to_string());
   add_hi(nickname.as_mut());
   just_print_stringy(nickname.as_ref());
}

Самый стандартный способ улучшить эргономику здесь - скрыть преобразования типов под капотом, абстрагируясь от типов ввода в наших API:

use std::convert::{AsRef,AsMut};
pub struct Nickname(String);

impl AsRef< str> for Nickname {
    fn as_ref(&self) -> &str {
        &self.0
    }
}
impl AsMut< String> for Nickname {
    fn as_mut(&mut self) -> &mut String {
        &mut self.0
    }
}
impl Nickname { 
    pub fn new< S: Into< String>>(nickname: S) -> Self {
        Self(nickname.into())
    }
}
pub fn just_print_stringy< S: AsRef< str>>(v: S) {
    println!("{}", v.as_ref())
}

pub fn add_hi< S: AsMut< String>>(mut v: S) {
    v.as_mut().push_str(" Hi")
}
fn main(){}

И теперь нашим API приятно пользоваться:

let mut nickname = Nickname::new("Vasya");
add_hi(&mut nickname);
just_print_stringy(&nickname);

Или через Cow

pub struct Nickname2< 'a>(Cow< 'a, str>);
impl< 'a> Nickname2< 'a> {
    pub fn new< S>(raw: S) -> Nickname2< 'a>
        where S: Into< Cow< 'a, str>>
    {
        Nickname2( raw.into() )
    }
}
impl < 'a>AsRef< str> for Nickname2< 'a> {
    fn as_ref(&self) -> &str {
        &self.0
    }
}
impl < 'a>AsMut< String> for Nickname2< 'a> {
    fn as_mut(&mut self) -> &mut String {
        self.0.to_mut()
    }
}

pub struct Nickname3(Cow< 'static, str>);
impl Nickname3 {
    pub fn new< S>(raw: S) -> Nickname3
        where S: Into< Cow< 'static, str>>
    {
        Nickname3( raw.into() )
    }
}
impl AsRef< str> for Nickname3 {
    fn as_ref(&self) -> &str {
        &self.0
    }
}
impl AsMut< String> for Nickname3 {
    fn as_mut(&mut self) -> &mut String {
        self.0.to_mut()
    }
}
fn main() {
   let mut nickname = Nickname2::new("Vasya");
   add_hi(&mut nickname);
   just_print_stringy(&nickname);
   
   let mut nickname = Nickname3::new("Vasya");
   add_hi(&mut nickname);
   just_print_stringy(&nickname);
}

Пример ссылка на внутренний тип


use std::convert::AsMut;
struct Child{
    data:Vec< i32>
}
struct Base{
    child:Child
}
impl AsRef< Child> for Base {
    #[inline]
    fn as_ref(&self) -> &Child { // as_ref Выполняет преобразование.
        &self.child
    }
}
impl AsMut< Child> for Base {
    #[inline]
    fn as_mut(&mut self) -> &mut Child { // as_mut Выполняет преобразование.
        &mut self.child
    }
}
fn main() {
    let c = Child{data:vec![1,2,3]};
    let mut base = Base{child:c};
    let link:&Child = base.as_ref();
    assert_eq!(link.data,[1,2,3]);

    let mut_link:&mut Child = base.as_mut();
    if let Some(el) = (*mut_link.data).get_mut(0){
        *el = 2;
    }
    assert_eq!(mut_link.data,[2,2,3]);
}

Используйте, Borrow когда цель абстрагироваться, нам нужно владеть значением или ссылаться ссылкой, нам это неважно любой тип T/&T/&mut T нам подойдут

В отличии от AsRef, Borrow налагает дополнительные ограничения: реализация Borrow< T> некоторым типом возможна, лишь если ссылка &T хэшируется и сравнивается точно так же, как значение, от которого она позаимствована.(как String и &str)

std::borrow::Borrow становится полезен, когда есть более одного вида занимаемого значения. Это особенно верно для ссылок и срезов: у вас может быть как &T, так и &mut T.

  • Используйте, Borrow когда цель связана с написанием кода, который не зависит от типа заимствования и является ли он ссылкой или значением

Например, Box< T> можно заимствовать как T, а String можно заимствовать как str, так как есть impl Borrow< str> for String и impl BorrowMut< str> for String

// подходит для `&mut T` и `&T` и `T` и `mut T`
use std::borrow::Borrow;
use std::fmt::Debug;

fn generic_borrow<T: Borrow<str> + Debug>(value: T) {
    println!("{:?}", value.borrow());   
}

fn main() {
    let s = String::from("Привет");
    generic_borrow(s.clone());          // String
    generic_borrow(&s);                 // &String
    generic_borrow(&mut s.clone());     // &mut String
    generic_borrow("Привет");           // &str
}
fn add_four<T: std::borrow::Borrow<i32>>(v: T) -> i32 {
    v.borrow() + 4
}
fn main(){
    assert_eq!(add_four(&2), 6);
    assert_eq!(add_four(2), 6);
}

Эта std::borrow::Borrow черта аналогична AsRef: если тип реализует Borrow<T>, то его метод заимствования эффективно заимствует &T у него. Но Borrow накладывает больше ограничений: тип должен реализовываться Borrow<T> только тогда, когда &T хэшируется и сравнивается так же, как и значение, из которого он заимствован. (Rust не обеспечивает этого; это просто документированное назначение этой особенности.)

Это Borrow полезно при работе с ключами в хэш-таблицах и деревьях или при работе со значениями, которые будут хэшироваться или сравниваться по какой-либо другой причине.

Это различие имеет значение при заимствовании из Strings, например: String реализует AsRef<str>, AsRef<[u8]> и AsRef<Path>, но эти три целевых типа обычно будут иметь разные значения хэш-функции. Только &str срез гарантированно хэшируется как эквивалент String, поэтому String реализует только Borrow<str>.

Borrow предназначен для решения конкретной ситуации с общими хэш-таблицами и другими типами ассоциативных коллекций.

В частности, Borrow он предназначен для гораздо более узкого случая использования: позволяет вызывающей стороне предоставить любой из нескольких практически идентичных вариантов одного и того же типа.

Возможно, Equivalent вместо этого его можно было бы вызвать. Например, для a HashSet<String> позволяет Borrow вызывающей стороне указать либо &str, либо &String. Хотя то же самое можно было бы достичь и с помощью AsRef, это было бы небезопасно без Borrow дополнительного требования о том, чтобы целевой тип реализовал Hash, Eq и Ord точно такой же, как реализующий тип`.

В общем, Borrow предназначен только для случаев, когда ваш тип по существу эквивалентен другому типу, тогда как Deref и AsRef предназначены для более широкой реализации для всего, что ваш тип может «действовать как»

use std::borrow::Borrow;
use std::borrow::BorrowMut;
use std::fmt::Debug;
#[derive(Debug,Clone)]
struct Wrap<T>(T);

impl<T> Borrow<T> for Wrap<T> {
    #[inline]
    fn borrow(&self) -> &T { &self.0}
}
impl<T> BorrowMut<T> for Wrap<T> {
    #[inline]
    fn borrow_mut(&mut self) -> &mut T {&mut self.0 }
}
impl<T> Borrow<T> for &Wrap<T> {
    #[inline]
    fn borrow(&self) -> &T { &self.0}
}
impl<T> Borrow<T> for &mut Wrap<T> {
    #[inline]
    fn borrow(&self) -> &T { &self.0}
}
impl<T> BorrowMut<T> for &mut Wrap<T> {
    #[inline]
    fn borrow_mut(&mut self) -> &mut T { &mut self.0}
}
impl<T> AsRef<T> for Wrap<T> {
    fn as_ref(&self) -> &T { &self.0}
}
impl<T> AsMut<T> for Wrap<T> {
    fn as_mut(&mut self) -> &mut T { &mut self.0}
}
 
// подходит для `&mut T` и `mut T` 
fn generic_borrow_mut<T:BorrowMut<String>+Debug>(mut value:T,data:&str){
    (*value.borrow_mut()).push_str(data);
    println!("{:?}",value.borrow());   
}
// подходит для `&mut T` и `&T` и `T` и `mut T`
fn generic_borrow<T:Borrow<String>+Debug>(value:T){
    println!("{:?}",value.borrow());  
}

// подходит для `&mut T` и `mut T`
fn generic_as_mut<T:AsMut<String>+>(mut value:T,data:&str){
    (*value.as_mut()).push_str(data);
    println!("{:?}",value.as_mut());
}
// подходит для `&mut T` и `&T` и `T` и `mut T`
fn generic_as_ref_shared<T:AsRef<String>>(value:T){
    println!("{:?}",value.as_ref()); 
}
fn main(){
   let mut w = Wrap::<String>(String::from("..."));
   let _:&mut String = w.borrow_mut();// // или <Wrap::<String> as BorrowMut<String>>::borrow_mut(&mut w);
   let _:&String = w.borrow();// или <Wrap::<String> as Borrow<String>>::borrow(&w); 
   
   let mut w = Wrap::<String>(String::from("..."));
   generic_borrow_mut(&mut w,"");// и &mut T
   generic_borrow(&mut w);
   generic_borrow(&w);// и &T
   generic_borrow_mut(w,"");// и T
   let w = Wrap(String::from("..."));
   generic_borrow(w);// и T

   let mut w = Wrap::<String>(String::from("..."));
   generic_as_mut(&mut w,"+");
   generic_as_mut(w,"+");
   let w = Wrap::<String>(String::from("..."));
   generic_as_ref_shared(&w);
   generic_as_ref_shared(w);
}
 

Не обобщенная реализация, только String

use std::borrow::Borrow;
use std::borrow::BorrowMut;
use std::fmt::Debug;
#[derive(Debug,Clone)]
struct Wrap(String);

impl Borrow<String> for Wrap {
    #[inline]
    fn borrow(&self) -> &String { &self.0}
}
impl BorrowMut<String> for Wrap {
    #[inline]
    fn borrow_mut(&mut self) -> &mut String { &mut self.0}
}
impl Borrow<String> for &Wrap {
    #[inline]
    fn borrow(&self) -> &String { &self.0}
}
impl Borrow<String> for &mut Wrap {
    #[inline]
    fn borrow(&self) -> &String { &self.0}
}
impl BorrowMut<String> for &mut Wrap {
    #[inline]
    fn borrow_mut(&mut self) -> &mut String { &mut self.0}
}
impl AsRef<String> for Wrap {
    fn as_ref(&self) -> &String { &self.0}
}
impl AsMut<String> for Wrap {
    fn as_mut(&mut self) -> &mut String {  &mut self.0 }
}
 
// подходит для `&mut T` и `mut T` 
fn generic_borrow_mut<T:BorrowMut<String>+Debug>(mut value:T,data:&str){
    (*value.borrow_mut()).push_str(data);
    println!("{:?}",value.borrow());   
}
// подходит для `&mut T` и `&T` и `T` и `mut T`
fn generic_borrow<T:Borrow<String>+Debug>(value:T){
    println!("{:?}",value.borrow());   
}

// подходит для `&mut T` и `mut T`
fn generic_as_mut<T:AsMut<String>+>(mut value:T,data:&str){
    (*value.as_mut()).push_str(data);
    println!("{:?}",value.as_mut());
}
// подходит для `&mut T` и `&T` и `T` и `mut T`
fn generic_as_ref_shared<T:AsRef<String>>(value:T){
    println!("{:?}",value.as_ref()); 
}
fn main(){
   let mut w = Wrap(String::from("..."));
   let _:&mut String = w.borrow_mut();

   let mut w = Wrap(String::from("..."));
   generic_borrow_mut(&mut w,"");// и &mut T
   generic_borrow(&mut w);
   generic_borrow(&w);// и &T
   generic_borrow_mut(w,"");// и T
   let w = Wrap(String::from("..."));
   generic_borrow(w);// и T


   let mut w = Wrap(String::from("..."));
   generic_as_mut(&mut w,"+");
   generic_as_mut(w,"+");
   let w = Wrap(String::from("..."));
   generic_as_ref_shared(&w);
   generic_as_ref_shared(w);
}

Где полезен Borrow

Характеристика std::borrow::Borrow похожа на AsRef: если тип реализует Borrow< T>, то его метод borrow фактически заимствует ссылку &T. Но Borrow налагает дополнительные ограничения: реализация Borrow< T> некоторым типом возможна, лишь если ссылка &T хэшируется и сравнивается точно так же, как значение, от которого она позаимствована. (Rust это не гарантирует; это всего лишь документированное назначение характеристики.) Поэтому Borrow особенно полезна при работе с ключами в хеш-таблицах и деревьях, а также со значениями, которые могут Хэшироваться или сравниваться по другим причинам.

Эта особенность начинает играть роль, например, при заимствовании у строк String: тип String реализует характеристики AsRef<&str> , AsRef<[u8]> и AsRef<Path> , но хеш-коды этих трех целевых типов обычно различаются. Гарантируется лишь, что &str хэшируется в то же значение, что эквивалентная строка String , так что String реализует только Borrow<str>

Характеристика Borrow предназначена для специальной ситуации, возникающей при работе с универсальными хеш-таблицами и другими типами ассоциативных коллекций. Какова должна быть сигнатура метода, который ищет запись в std::collections::HashMap<String, i32>? Т.е. по идее мы должны передавать ключ типа String но это расточительно. Если передавать как &String тогда при наличии &str ключа придется преобразовывать в String ".get(&"twenty-two".to_string())"

Если мы хотим использовать для поиска ключ типа &String и &str то сигнатура должна быть через Borrow:

use std::borrow::Borrow;
fn check_borrow<T: Borrow<str>+std::fmt::Display>(s: T) {
    assert_eq!("Hello", s.borrow());//borrow() неизменяемое заимствование
    validate(s.borrow());
    print!("{}",s);
}
 
fn validate(value:&str)->bool{
    true
}

check_borrow("Hello".to_string());
check_borrow("Hello");

// Into тоже подходит для входного String или &str
use core::fmt::Debug;
fn check_into<N: Into<String>+Debug>(msg: N) {
    print!("{:?}",msg.into());
}

check_into("Hello");
check_into("Hello".to_string());
// -------------------------------------------------------------------
fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
 where K: Borrow<Q>,                
           Q: Eq + Hash
 { ... }

fn main(){}
use std::fmt::Debug; 
use std::borrow::Borrow;
 use std::fmt::Display; 
use std::hash::Hash;

pub struct HashMap<K, V> {
  fields:Vec<(K,V)>
}

impl<K, V> HashMap<K, V> {
    pub fn insert(&mut self, key: K, value: V)  
    {
        self.fields.push((key,value));
    }

    pub fn get<Q>(&self, k: &Q) -> Option<&V>
    where
        K: Borrow<Q>,
        Q: Hash + ?Sized
    {
        Some(&self.fields[0].1)
    }
}
fn main() {
    let mut h = HashMap{fields:vec![]};
    // сохраняем String ,HashMap владеит
    h.insert("KEY".to_string(),"VALUE".to_string());
    // поиск через &str
    println!("{:?}",h.get("KEY"));
}

Trait std::borrow::BorrowMut

fn check<T: BorrowMut<[i32]>>(mut v: T) {
    //assert_eq!(&mut [1, 2, 3], v.borrow_mut());
    let vb  = v.borrow_mut();
    
    for elem in vb.iter_mut() {
       *elem += 2;
     }
   assert_eq!(&mut [3, 4, 5], v.borrow_mut());
    // print!("{:?}",vb);
}
fn main(){
    let v = vec![1, 2, 3];
    check(v);
}

Эти трейты были изобретены для решения очень специфической проблемы поиска String ключей в HashSets, HashMaps, BTreeSets и BTreeMaps с использованием &str значений.

Мы можем рассматривать Borrow<T> и BorrowMut<T> как более строгие версии AsRef<T>и AsMut<T>, где возвращаемая ссылка &T эквивалентна Eq, Hash и Ord означает Self. Это легче объяснить на примере с комментариями:

Хорошо знать об этих трейтах и ​​понимать, почему они существуют, так как это помогает демистифицировать некоторые методы в HashSet, HashMap, BTreeSet, и BTreeMap, но очень редко нам когда-нибудь понадобится применять эти трейты для любого из наших типов, потому что очень редко мы когда-нибудь понадобится создать пару типов, где один является «заимствованной» версией другого. Если у нас есть какие-то, T то &T работа будет выполнена в 99,99% случаев, и T: Borrow<T> она уже реализована для всех T из-за общего внедрения одеяла, поэтому нам не нужно внедрять его вручную, и нам не нужно создавать что-то U такое, что T: Borrow<U>

use std::borrow::Borrow;
use std::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;

fn get_hash<T: Hash>(t: T) -> u64 {
    let mut hasher = DefaultHasher::new();
    t.hash(&mut hasher);
    hasher.finish()
}

fn asref_example<Owned, Ref>(owned1: Owned, owned2: Owned)
where
    Owned: Eq + Ord + Hash + AsRef<Ref>,
    Ref: Eq + Ord + Hash
{
    let ref1: &Ref = owned1.as_ref();
    let ref2: &Ref = owned2.as_ref();
    
    // refs aren't required to be equal if owned types are equal
    assert_eq!(owned1 == owned2, ref1 == ref2); // ❌
    
    let owned1_hash = get_hash(&owned1);
    let owned2_hash = get_hash(&owned2);
    let ref1_hash = get_hash(&ref1);
    let ref2_hash = get_hash(&ref2);
    
    // ref hashes aren't required to be equal if owned type hashes are equal
    assert_eq!(owned1_hash == owned2_hash, ref1_hash == ref2_hash); // ❌
    
    // ref comparisons aren't required to match owned type comparisons
    assert_eq!(owned1.cmp(&owned2), ref1.cmp(&ref2)); // ❌
}

fn borrow_example<Owned, Borrowed>(owned1: Owned, owned2: Owned)
where
    Owned: Eq + Ord + Hash + Borrow<Borrowed>,
    Borrowed: Eq + Ord + Hash
{
    let borrow1: &Borrowed = owned1.borrow();
    let borrow2: &Borrowed = owned2.borrow();
    
    // borrows are required to be equal if owned types are equal
    assert_eq!(owned1 == owned2, borrow1 == borrow2); // ✅
    
    let owned1_hash = get_hash(&owned1);
    let owned2_hash = get_hash(&owned2);
    let borrow1_hash = get_hash(&borrow1);
    let borrow2_hash = get_hash(&borrow2);
    
    // borrow hashes are required to be equal if owned type hashes are equal
    assert_eq!(owned1_hash == owned2_hash, borrow1_hash == borrow2_hash); // ✅
    
    // borrow comparisons are required to match owned type comparisons
    assert_eq!(owned1.cmp(&owned2), borrow1.cmp(&borrow2)); // ✅
}
fn main(){}

Время жизни - это конструкция, которую компилятор (или более конкретно, его анализатор заимствований) использует, чтобы убедиться, что все заимствования действительны. Времена жизни и области видимости не совпадают.

ресурсы могут иметь лишь одного владельца, ссылки не владеют ресурсами

При заимствовании ресурса по ссылке он освобождается при выходе из области видимости (заимствование закончилось) но если мы решаем его снова использовать по той же ссылке то будет ошибка доступа к памяти которая освобождена ( «висячий указатель» или «использование после освобождения») мы не должны использовать освобожденный ресурс !

система Rust делает это с помощью действительной ссылки на времени жизни ресурса в области видимости

Мы должны убедиться, что ссылка на ссылающийся ресурс не может жить дольше, чем ссылка с которой она получена на этот ресурс

Только элементы, связанные с ссылками (например, такие как структура, содержащая ссылку) требуют указания времени жизни

Присвоение имени времени жизни — это способ задать имя области видимости. Чтобы думать о чём-то, нужно иметь название для этого.

'static str хранит указатель на строку в stack, но сами данные хранятся статично в бинарном скомпилированном файле программы пожизненно

data memory (память данных) - для данных фиксированного размера и статических данных (доступные в любой момент времени выполнения программы). Рассмотрим текст в вашей программе (пример строка "Hello World!"). Эта строка является набором байт, которые нельзя изменить и можно только считать, поэтому они могут сохраняться в данном регионе. Компиляторы делают очень много оптимизаций с таким видом данных. Этот регион памяти считается очень быстрым, так как местоположение данные известно и фиксировано заранее.

graph TD A[STACK ... free space ... HEAP ... UNINITIALIZED DATA *bss* ... INITIALIZED DATA *data* ... TEXT]

&'static T и T: 'static - не одно и тоже

&'a T и T: 'a - не одно и то же

lifetime

T: 'static следует читать как «T ограничен сроком службы 'static», а не как «T имеет срок службы 'static»

Тип с временем жизни 'static (&'static T) отличается от типа, ограниченного временем жизни 'static (T: 'static). Ограничение T временем 'static могут быть динамически выделены во время выполнения, могут быть безопасно и свободно изменены, могут быть удалены и могут существовать произвольное время. Ограничение T:'static включает все, &'static T однако он также включает в себя все принадлежащие типы, такие как String, Vec и т. д

Можно генерировать случайные динамически распределяемые данные во время выполнения и возвращать ссылки 'static на них за счет утечки памяти, например

use rand;
// generate random 'static str refs at run-time
fn rand_str_generator() -> &'static str {
    let rand_string = rand::random::<u64>().to_string();
    Box::leak(rand_string.into_boxed_str())
}
fn main(){}

use rand;

fn drop_static<T: 'static>(t: T) {
    std::mem::drop(t);
}

fn main() {
    let mut strings: Vec<String> = Vec::new();
    strings.push("string".to_string());
    strings.push("string".to_string());

    // строки являются принадлежащими типами, поэтому они ограничены 'static
    for mut string in strings {
      
        string.push_str("a mutation");// все строки изменяемы
        
        drop_static(string); // ✅ все строки сбрасываются
    }
}
// пропущенный
fn print(s: &str){unimplemented!()} ✅ 

// расширенный
fn print2<'a>(s: &'a str){unimplemented!()} ✅ 

// пропущенный
fn trim(s: &str) -> &str{unimplemented!()} ✅ 

// расширенный
fn trim2<'a>(s: &'a str) -> &'a str{unimplemented!()} ✅ 

// недопустимо, невозможно определить время жизни вывода, нет входных данных
fn get_str() -> &str{unimplemented!()} ❌

// явные параметры включают
fn get_str2<'a>() -> &'a str{unimplemented!()} // ✅  generic version
fn get_str3() -> &'static str{unimplemented!()} // ✅  'static version

// недопустимо, невозможно определить время жизни вывода, несколько входов
fn overlap(s: &str, t: &str) -> &str{unimplemented!()} ❌

// явные (но все же частично опущенные) опции включают
fn overlap1<'a>(s: &'a str, t: &str) -> &'a str{unimplemented!()} // ✅  output can't outlive s
fn overlap2<'a>(s: &str, t: &'a str) -> &'a str{unimplemented!()} // ✅  output can't outlive t
fn overlap3<'a>(s: &'a str, t: &'a str) -> &'a str{unimplemented!()} // ✅  output can't outlive s & t
fn overlap4(s: &str, t: &str) -> &'static str{unimplemented!()} // ✅  output can outlive s & t
fn overlap5<'a>(s: &str, t: &str) -> &'a str{unimplemented!()} // ✅  no relationship between input & output lifetimes

// расширенный
fn overlap6<'a, 'b>(s: &'a str, t: &'b str) -> &'a str{unimplemented!()} ✅ 
fn overlap7<'a, 'b>(s: &'a str, t: &'b str) -> &'b str{unimplemented!()} ✅ 
fn overlap8<'a>(s: &'a str, t: &'a str) -> &'a str{unimplemented!()} ✅ 
fn overlap9<'a, 'b>(s: &'a str, t: &'b str) -> &'static str{unimplemented!()} ✅ 
fn overlap10<'a, 'b, 'c>(s: &'a str, t: &'b str) -> &'c str{unimplemented!()} ✅ 

// пропущенный
fn compare(&self, s: &str) -> &str{unimplemented!()} ✅ 

// расширенный
fn compare2<'a, 'b>(&'a self, &'b str) -> &'a str{unimplemented!()} ✅ 
fn main(){}

Три правила

lifetime-syntax

Первое правило каждый параметр получает свое собственное время жизни

   fn foo<'a>(x: &'a i32)
   fn foo<'a, 'b>(x: &'a i32, y: &'b i32)

Второе правило если существует точно один входной параметр времени жизни, то его время жизни назначается всем выходным параметрам:

   fn foo<'a>(x: &'a i32) -> &'a i32

Третье правило заключается в том, что если есть множество входных параметров времени жизни, но один из них является ссылкой &self или &mut self при условии, что эта функция является методом структуры или перечисления, то время жизни self назначается временем жизни всем выходным параметрам метода. Только для методов

Код с ошибкой так как компилятор не может применить ни одно правило и, следовательно, не знает времени жизни возвращаемого значения. Надо задать конкретное время жизни возвращаемого значения связав его с входным параметром -> & 'a str или -> & 'b st

   fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {  "" }  =>    
   fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {  "" } 
 // или 
   fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'b str {  "" }
Вот пример, когда применяется правило третьего срока жизни:
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

Существует два периода времени вхождения, поэтому Rust применяет первое правило элиминации жизни и дает оба &self и announcement свои собственные времена жизни. Тогда, поскольку один из параметров - это &self, тип возврата получает время жизни &self, и учитываются все времена жизни.

Ресурсы освобождаются в обратном порядке их инициализации


fn main(){
    let y:&i32;
    let x = 5;
    y = &x; // Ошибка `x` does not live long enough
   // Сначало удалится `x` а он содержит данные на которые ссылается `y`
}

Ограничение владельца ссылки

borrows

Например, попытка обновить элемент через его владельца эквивалентна созданию эфемерной изменяемой ссылки и последующему обновлению элемента через эту ссылку. Если другая ссылка уже существует, эта воображаемая вторая изменяемая ссылка не может быть создана:


fn main(){
 let mut item = Item { contents: 42 };
 let r = &item;
 item.contents = 0;
 // ^^^ Changing the item is roughly equivalent to:
 //   (&mut item).contents = 0;
 println!("reference to item is {:?}", r);
}

Наличие какой-либо активной ссылки не позволяет владельцу предмета перемещать или удаление элемента, именно потому, что это означало бы, что ссылка теперь ссылается на недействительный элемент:


fn main(){
 let item = Item { contents: 42 };
 let r = &item;
 let new_item = item; // move
 println!("reference to item is {:?}", r);
}

Область видимости

nomicon/lifetimes

Каждый let оператор неявно вводит область видимости:


fn main(){
 let x = 0;
 let z;
 let y = &x;
 z = y;
}

Раскроется в это:


fn main(){
 'a: {
    let x: i32 = 0;
    'b: {
        let z: &'b i32;
        'c: {
            // Здесь необходимо использовать 'b, потому что эта ссылка передается в эту область
            let y: &'b i32 = &'b x;
            z = y;
        }
    }
 }
}

Следующий фрагмент компилируется, поскольку после печати xон больше не нужен, поэтому не имеет значения, висит ли он или имеет псевдоним (хотя x технически переменная существует до самого конца области видимости).


fn main(){
    let mut data = vec![1, 2, 3];
    let x = &data[0];
    println!("{}", x);
    data.push(4);
}

Если значение имеет деструктор, деструктор запускается в конце области. А запуск деструктора считается использованием — очевидно, последним.


fn main(){
    #[derive(Debug)]
    struct X<'a>(&'a i32);
    
    impl Drop for X<'_> {
        fn drop(&mut self) {}
    }
    
    let mut data = vec![1, 2, 3];
    let x = X(&data[0]);
    // drop(x); // - Один из способов убедить компилятор в том, что x больше недействителен, — использовать drop(x) до data.push(4)
    data.push(4);
}

Компилятор не может понять что ссылки shared а не mut

limits-of-lifetimes


#[derive(Debug)]
struct Foo;

impl Foo {
    fn mutate_and_share (& mut self) -> &Self { &*self }
    fn share(&self) {}
}

fn main() {
    let mut foo = Foo;
    let loan:&Foo = {
      foo.mutate_and_share()
    };
    
    foo.share();
    println!("{:?}", loan);
}

Вернуть &str

Если время жизни выхода опущено, то оно должно быть ограничено временем жизни входа.

unbounded-lifetimes


fn get_str<'a>() -> &'a str{
     "hello"
}
fn main(){ 
  let r = get_str();
  println!("{}",r);
}

Вернуть &static

Время 'static жизни используется для ссылок на элементы, которые гарантированно никогда не выйдут из области действия, например, глобальные данные или элементы в куче, которые явно утекли.

effective-rust/lifetimes

Что произойдет, если времени жизни входных ссылок нет, но возвращаемое выходное значение в любом случае включает ссылку? Единственная допустимая возможность — это чтобы возвращаемая ссылка имела время жизни, которое гарантированно никогда не выйдет за пределы области действия.

1. Самый простой способ получить что-то со 'static временем жизни — это взять ссылку на глобальную переменную, которая была отмечена как static: Компилятор Rust гарантирует, что static элемент всегда имеет один и тот же адрес на протяжении всей программы и никогда не перемещается.

static ANSWER: Item = Item { contents: 42 };
pub fn the_answer() -> &'static Item {
    &ANSWER
}
fn main(){}

2. Во многих случаях ссылка на const элемент также будет повышена до значения, имеющего 'static время жизни, но есть несколько небольших осложнений

  • вовлеченный тип не должен иметь деструктор или внутреннюю изменчивость
  • компилятор может подставлять в вызовы констант их копии так что не полагайтесь на адреса они будут разные

3. Если соблюдается условие - продолжительность жизни должна пережить любую другую продолжительность жизни в программе; значение, которое выделяется на куче, но никогда не освобождаемая

fn main(){
{
    let boxed = Box::new(Item { contents: 12 });

    // `leak()` consumes the `Box<T>` and returns `&mut T`.
    let r: &'static Item = Box::leak(boxed);

    println!("'static item is {:?}", r);
 } // 'boxed' здесь не отброшен, так как он уже был перемещен в 'Box::leak()'
}
// Поскольку `r` теперь находится вне области действия, элемент утерян навсегда.

Невозможность выбросить предмет также означает, что память, в которой хранится предмет, никогда не может быть восстановлена ​​с помощью безопасного Rust, что может привести к постоянной утечке памяти. (Обратите внимание, что утечка памяти не нарушает гарантии безопасности памяти Rust — элемент в памяти, к которому вы больше не можете получить доступ, по-прежнему в безопасности.)

for<'a> обьяснение

fn copy_if<'a, F>(slice: &[i32], pred: F) -> Vec<i32> 
 where  F: Fn(&'a i32) -> bool {
      let mut result = vec![];
       for &element in slice {
          if pred(&element) {
            result.push(element);
          }
       }
    result
}
fn main(){}

Компилятор дает следующую ошибку:

error: element does not live long enough

if pred(&element) {
^~~~~~~

потому что локальная переменная element не живет так долго, как lifetime 'a» (как мы видим из комментариев кода).

Время жизни не может быть объявлено на уровне функции, потому что нам нужно другое время жизни. Вот почему нам нужен for<'a> : чтобы указать, что ссылка может быть действительной для любого времени жизни (следовательно, можно использовать меньшее время жизни).

fn copy_if<F>(slice: &[i32], pred: F) -> Vec<i32> 
 where for<'a> F: Fn(&'a i32) -> bool {
    let mut result = vec![];
    for &element in slice {
        if pred(&element) {
            result.push(element);
        }
    }
    result
}
fn main(){}

Границы характеристик с более высоким рейтингом

for<'a>

Как передать время жизни во входные аргументы передаваемой функции?

higher-ranked-trait-bounds

fn function<F>(f: F) where for<'a> F: FnOnce(&'a Type)
struct Struct<F> where for<'a> F: FnOnce(&'a Type) { x: F }
enum Enum<F> where for<'a> F: FnOnce(&'a Type) { Variant(F) }
impl<F> Struct<F> where for<'a> F: FnOnce(&'a Type) { fn x(&self) -> &F { &self.x } }
fn main(){}

Границы типа могут иметь более высокий рейтинг в течение срока службы. Эти границы указывают, что ограничение истинно для всех времен жизни. Например, для такой привязки for<'a> &'a T: PartialEq<i32> потребуется реализация типа

struct T(i32);
 
impl<'a> std::cmp::PartialEq<i32> for &'a T {
   fn eq(&self, other: &i32) -> bool {self.0==*other}
}

impl<'a> std::cmp::PartialEq<i32> for T {
   fn eq(&self, other: &i32) -> bool {self.0==*other}
}
и затем может использоваться для сравнения &'a T с любым временем жизни с i32
fn main() {
  let t = T(4);
  
  if &t == 4{
      println!("Отработал \"for &'a T\"");
  }
  if t == 4{
      println!("Отработал \"for T\"");
  }
}

Здесь может использоваться только граница с более высоким рейтингом, потому что время жизни ссылки короче, чем любой возможный параметр времени жизни в функции:

fn call_on_ref_zero< F>(f: F)  where for<'a> F: Fn(&'a str)->&'a str {
    let zero:String = "0".to_owned();
     f(&zero) ;
}
fn main(){}

Время жизни с более высоким рейтингом также может быть указано непосредственно перед признаком: единственное различие - это область действия параметра времени жизни, который распространяется только на конец следующего признака, а не на всю границу. Эта функция эквивалентна предыдущей.

fn call_on_ref_zero_2<F>(f: F) where F: for<'a> Fn(&'a str)->&'a str {
    let zero:String = "0".to_owned();
    f(&zero)
}

fn foo(z:&str)->&str{
    println!("z={}",z);
    z
}
call_on_ref_zero(foo);
fn main(){}

1. Фактическое времени жизни переменных

Время жизни в трех ситуациях

Время жизни переменной - это время, в течение которого она существует.


fn main(){
{
  let x: Vec = Vec::new();  // ----------------------+
  {//                                                     |
      let y = String::from("Why");// ------+              |
    //                                     | y's lifetime |
  } // <-----------------------------------+              |
} // <----------------------------------------------------+
}

2. Ограничений времени жизни

Время жизни в трех ситуациях

Если бы это ограничение не было добавлено, x можно получить доступ к недопустимой памяти в строке println!, потому x что ссылка на y которую будет уничтожена в предыдущей строке. x время жизни пользователя вышло за пределы y срока жизни пользователя. Следовательно, этот код не компилируется.


fn main(){
// error:`y` does not live long enough 
{
  let x: &Vec 
  {                                                    
     let y = Vec::new(); // ----+              
     //                         | y's lifetime  
     x = &y; // ----------------|---------------+
     //                         |               | x's lifetime
  } // <------------------------+               |
  println!("x's length is  {}", x.len()); //    |
} // <------------------------------------------+
}

3. Аннотаций времени жизни

Время жизни в трех ситуациях

Когда компилятор не может сам вывести время жизни он нуждается в явном ручном указании аннотации времени жизни


fn print_ret_error(s1: &str, s2: &str) -> &str {
    println!("s1 is {}", s1);
    s2
}
// явно уточним срок действия ссылки
fn print_ret<'a>(s1: &str, s2: &'a str) -> &'a str {
    println!("s1 is {}", s1);
    s2
}
fn main() {
    let some_str: String = "Some string".to_string();
    let other_str: String = "Other string".to_string();
    let s1 = print_ret(&some_str, &other_str);
}

когда один и тот же параметр времени жизни аннотирует два или более аргумента функции, возвращаемая ссылка не должна превышать наименьшее время жизни аргументов


fn min<'a>(x:&'a i32, y: &'a i32) -> i32{
    if x < y{
        x
    } else{
        y
    }
}
fn main(){
    let p = 42; //--------------------------------------------------+ 
    { //                                                            | p's lifetime
        let q = 10; //-------------------------------+              |
        let r = min(&p, &q); //-------+              | q's lifetime |
        println!("Min is {}", r); //  | r's lifetime |              |
    } //------------------------------+--------------+              |
}//-----------------------------------------------------------------+

время жизни 'a не меньше, чем время жизни 'b

fn add_with_lifetimes<'a:'b, 'b>(i: &'a i32, j: &'b i32) -> &'b i32 {
     if *i > 0 {
         return i;
     }else{
         return j;
     }
}
fn main() {
   let a = 1;
   let b = 2;
   println!("{}", add_with_lifetimes(&a,&b));
}

<'a: 'b, 'b> читается как "время жизни 'a не меньше, чем время жизни 'b". Здесь мы получаем &'a i32 и в результате приведения возвращаем &'b i32.

fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first
}

fn main() {
    let first = 2; // Более длинное время жизни
    
    {
        let second = 3; // Более короткое время жизни
        
        println!("Произведение равно {}", multiply(&first, &second));
        println!("{} первое", choose_first(&first, &second));
    };
}
// или привести к меньшему времени

// Здесь Rust выводит наиболее короткое время жизни.
// Затем обе ссылки приводятся к этому времени жизни.
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
    first * second
}

Распространенные заблуждения о временах жизни в Rust

common-rust-lifetime-misconceptions

Заблуждения

  1. T содержит только владеющие типы
  2. если T: 'static, то T должно жить на протяжении всего времени работы программы
  3. &'a T и T: 'a — это одно и то же
  4. мой код не является обобщённым и не имеет времён жизни
  5. если оно компилируется, то мои аннотации времён жизни верны
  6. трейт-объекты за владеющими указателями не имеют времён жизни
  7. сообщения об ошибках компиляции скажут мне, как исправить мою программу
  8. времена жизни могут расти и сокращаться в рантайме
  9. ослабление mut-ссылок до разделяемых безопасно
  10. замыкания следуют тем же правилам вывода времён жизни, что и функции
  11. 'static ссылки всегда можно привести к 'a

1. Явно ограничить время жизни {}

non-lexical-lifetimes-introduction

Мы не можем использовать данные которые хранятся по мутабельной ссылке в slice

fn bar() {
    let mut data = vec!['a', 'b', 'c'];
    let slice = &mut data[..]; // <-+ 'lifetime
    capitalize(slice);         //   |
    data.push('d'); // ERROR!  //   |
    data.push('e'); // ERROR!  //   |
    data.push('f'); // ERROR!  //   |
} // <------------------------------+
fn main(){}

Явно ограничим время жизни мутабельной ссылки slice

fn bar() {
    let mut data = vec!['a', 'b', 'c'];
    {
        let slice = &mut data[..]; // <-+ 'lifetime
        capitalize(slice);         //   |
    } // <------------------------------+
    data.push('d'); // OK
    data.push('e'); // OK
    data.push('f'); // OK
}
fn main(){}

2. Проблема при условном потоке управления дважды заимствовать мутабельно нельзя

non-lexical-lifetimes-introduction

Сегодня этот код компилироваться не будет. Причина в том, что map заимствуется как часть вызова get_mut, и это заимствование должно охватывать не только вызов get_mut, но и Some ветвь совпадения. Самым внутренним выражением, содержащим оба этих выражения, является само совпадение (как показано выше), и, следовательно, считается, что заимствование продолжается до конца совпадения. К сожалению, совпадение включает не только Some ветку, но и None ветку, и, следовательно, когда мы переходим к вставке на карту в None ветке, мы получаем сообщение об ошибке, что map все еще заимствовано.

fn process_or_default<K,V:Default>(map: &mut HashMap<K,V>,key: K) {
    match map.get_mut(&key) { // ------------+ 'lifetime
        Some(value) => process(value),    // |
        None => {                         // |
            map.insert(key, V::default());// |
            //  ^~~~~~ ERROR.             // |
        }                                 // |
    } // <-----------------------------------+
}
fn main(){}

Решение это выйти за пределы первого мутабельного заимствования

fn process_or_default1<K,V:Default>(map: &mut HashMap<K,V>, key: K) {
    match map.get_mut(&key) { // -------------+ 'lifetime
        Some(value) => {                   // |
            process(value);                // |
            return;                        // |
        }                                  // |
        None => {                          // |
        }                                  // |
    } // <------------------------------------+
    map.insert(key, V::default());
}
fn main(){}

3. Проблема при условном потоке управления ограничение программы проверки заимствований

problem-case-3-conditional-control-flow-across-functions

Когда вы возвращаете ссылку из функции

Рассмотрим следующую функцию, которая возвращает значение ключа, если он существует, и вставляет новое значение в противном случае (для целей этого раздела предположим, что entry API для карт не существует)

fn get_default<'m,K,V:Default>(map: &'m mut HashMap<K,V>,key: K) -> &'m mut V {
    match map.get_mut(&key) { // -------------+ 'm
        Some(value) => value,              // |
        None => {                          // |
            map.insert(key, V::default()); // |
            //  ^~~~~~ ERROR               // |
            map.get_mut(&key).unwrap()     // |
        }                                  // |
    }                                      // |
}                                          // v
fn main(){}

Причина в том, что в Some ветке значение возвращается вызывающей стороне. Поскольку value это ссылка на map, это означает, что объект map будет оставаться заимствованным до некоторой точки 'm в вызывающей стороне

fn caller() {
    let mut map = HashMap::new();
    ...
    {
        let v = get_default(&mut map, key);         // -+ 'm
          // +-- get_default() -------------------+     |
          // | match map.get_mut(&key) {          |     |
          // |   Some(value) => value,            |     |
          // |   None => {                        |     |
          // |     ..                             |     |
          // |   }                                |     |
          // +------------------------------------+     |
        process(v);                                     |
    } // <----------------------------------------------+
    ...
}
fn main(){}

В то время как до того, как время жизни value было ограничено совпадением (2. Проблема при условном потоке управления), это новое время жизни распространяется на вызывающую, и поэтому заимствование не заканчивается только потому, что мы вышли из совпадения. Следовательно, он все еще находится в области действия, когда мы пытаемся вызвать insert после совпадения.

fn get_default2<'m,K,V:Default>(map: &'m mut HashMap<K,V>, key: K)  -> &'m mut V {
    if map.contains(&key) {
    // ^~~~~~~~~~~~~~~~~~ 'n
        return match map.get_mut(&key) { // + 'm
            Some(value) => value,        // |
            None => unreachable!()       // |
        };                               // v
    }
    // тут никогда get_mut до этого не вызывался т. е. нет второго заимствования
    map.insert(key, V::default()); // OK now.
    map.get_mut(&key).unwrap()
}
fn main(){}

Блок if дал нам область, где заимствование начинается в точке get_mut, и это заимствование длится до точки 'm в вызывающей стороне, и программа проверки заимствований может видеть, что это заимствование даже не началось за пределами if. Таким образом, он не рассматривает объем заимствования в том месте, где мы вызываем map.insert

Если использовать специально написанный entry API, то:

fn get_default3<'m,K,V:Default>(map: &'m mut HashMap<K,V>,key: K) -> &'m mut V {
    map.entry(key).or_insert_with(|| V::default())
}
fn main(){}

Если lifetime не указаны явно, то для каждого '_ в параметрах создаётся свой уникальный lifetime, а возвращаемое значение получает lifetime self или уникальное lifetime параметра. Lifetime Elision компилятор не смотрит на тело функции.

struct Person;

impl Person {
// По умолчанию время жизни берется из self
// о так как мы возвращаем y у него другое время жизни - ошибка
// this parameter and the return type are declared with different lifetimes...
    /*fn test(&self,y:&i32)->&i32{ // на самом деле компилято добавляет сахар fn test<'a,'b>(&'a self,y:&'b i32)->&'a i32 // а нам нужно время жизни 'b
        y
    }*/
    
    fn test<'a,'b>(&'a self,y:&'b i32)->&'b i32{
        y
     }
    // или указать время жизни только для y
    // для остальных ссылок будет созданно автоматически Elision
    fn test2<'b>(&self,y:&'b i32)->&'b i32{
        y
    }
}
fn main(){}

Ошибка использования времени жизни неявно задаваемая компилятором

struct Ref<'a, T> {
    r: &'a T
}
impl<'a, T> Ref<'a, T> {
// В самой ф-ции get ошибки нет но будет когда попытаемся ее использовать
// и она будет не в этой ф-ции get а в используемом коде 

    // self будет иметь время жизни не 'a и оно будет меньше чем 'a но мы его возвращаем
    fn get(&self) -> &T {
      self.r
    }
    //т.е. такой сахар добавит компилятор
    fn get<'b>(&'b self) -> &'b T {
     self.r // сократили lifetime от 'a до 'b !!!
    }
    fn get(&self) -> &'a T { // тут верно дали явно время жизни 'a 
      self.r
    }
}

// Ошибка при использовании
fn unwrap_ref(r: Ref<'_, i32>) -> &i32 {
    r.get() // cannot return value referencing function parameter `r` ХОТЯ ОШИБКА НЕ ЗДЕСЬ А В Impl где мы неверное время жизни возвращаем данное компилятором
}
// эту ф-цию компилятор создаст с таким сахаром
// Так как r.get() работает через ссылку &self
// то компилятор создает локальную переменную и берет от нее ссылку
// и получаем время жизни временной переменной а не аргумента r и получаем ошибку
fn unwrap_ref<'a>(r: Ref<'a, i32>) -> &'a i32 {
    let r: &Ref<'a, i32> = &r;
    r.get() // вж временной переменной
}
fn main(){}

Возвращаемая ссылка не должна пережить свой обьект


#[derive(Debug)]
struct Person{
    first:String
}
// ошибка, структура удалиться раньше чем время жизни ссылки на эти данные
fn hello() -> &str {
    let person = Person {
        first: "Yehuda".to_string()
    };
    person.first.as_str()
}
fn main(){
   let mut v  = vec![];
   let s = hello(&mut v);
   println!("{}",s);
}

Решение:


#[derive(Debug)]
struct Person{
    first:String
}
fn hello(v:&mut Vec) -> &str {
    let person = Person {
        first: "Yehuda".to_string()
    };
    v.push(person);
    v[0].first.as_str()
}

fn main(){
   let mut v  = vec![];
   let s = hello(&mut v);
   println!("{}",s);
}

Увеличим время жизни до ее функции

fn search_case_insensitive(s: &str) -> bool {
    let s: &str = if search_lowercased(s) {
        s
    } else {
        &s.to_lowercase() // to_lowercase возвращает String и далее мы возвращаем локальную ссылку &String. Мы не можем с этим локальным lifetime вызвать search_lowercased, так как он удалиться раньше 
        /* тоже самое
         let temp = s.to_lowercase();
         &temp
        */
    };
    /*в этом месте удалиться &temp т.е. еще до search_lowercased */
    search_lowercased(&s)
}
fn main(){}

следует преобразовать в:

pub fn search_case_insensitive(s: &str) -> bool {
    let lowercased: String;
    let s: &str = if search_lowercased(s) {
           s
        } else {
            lowercased = s.to_lowercase();
            &lowercased
    };
    search_lowercased(&s)
}
fn search_lowercased(s: &str) -> bool {
   // ....
   true
}
fn main(){}

Несколько времён жизни (Multiple lifetimes)

Если вы имеете несколько ссылок, вы можете использовать одно и то же время жизни несколько раз:

  • x и y имели одно время жизни
  • возвращаемое значение живо на протяжении той же области видимости 'a
fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {x }
fn main(){}

  • x и y имели разные времена жизни
  • возвращаемое значение живо на протяжении той же области видимости a что и x
fn x_or_y2<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {x}
fn main(){}

Ссылка 'a будет жить не менее чем ссылка 'b. Lifetime subtyping (под типирование)

fn x_or_y3<'a:'b,'b>(x: &'a str, y: &'b str) -> &'a str {x}
fn main(){}

Lifetime subtyping Подтипирование времени жизни: обеспечивает, чтобы одна продолжительность жизни переживала другую жизнь


struct User<'u>{
   name:&'u str
}
// Обьяснили компилятору что время жизни 'u будет жить по крайней мере до тех пор, пока живет 'b
struct Boxed<'b,'u:'b>{
   user:&'b User<'u >
}
fn main(){
   let user:User = User{name:"Jeka"};
   let _box:Boxed = Boxed{user:&user};
}

Lifetime subtyping

// три значения времени жизни, объявленные в этом примере, не связаны.
struct Context<'a>(&'a str);

struct Parser<'a> {
    context: &'a Context<'a>,
}

impl<'a> Parser<'a> {
// Допустим вернем ошибку при анализе ввода, которая ссылается на часть недопустимого ввода; эта ссылка делает пример кода интересным в отношении времен жизни
// И допустим  что ввод недействителен после первого байта
    fn parse(&self) -> Result<(), &str> {
        Err(&self.context.0[1..])
    }
}
// Он сообщает Rust, что  Parser содержит ссылку на Context с временем жизни 'a и Context содержит строковый срез, 
// который также живет до тех пор, пока ссылка на Context находится в Parser



// Ошибка
//parse_context принимает экземпляр Context, использует Parser для разбора этого контекста и возвращает parse.
fn parse_context(context: Context) -> Result<(), &str> {
    Parser { context: &context }.parse()
}
// Эти ошибки Parser указывают, что созданный экземпляр и context параметр живут только до конца parse_context функции. Но им нужно жить всю жизнь функции
// Другими словами, Parser и context нужно пережить все функции и действительны до функции начинается, а также после его окончания для всех ссылок в этом коде , чтобы всегда быть в силе.


// fn parse(&self) -> Result<(), &str> {..
Помните правила элиции elision rules ? Если мы аннотируем время жизни ссылок, а не возвращаем, подпись будет следующей:
// fn parse<'a>(&'a self) -> Result<(), &'a str> {

// То есть часть ошибки возвращаемого значения parseимеет время жизни, которое привязано к времени жизни Parserэкземпляра ( &selfв parse сигнатуре метода).
// Это имеет смысл: возвращенный фрагмент строки ссылается на срез строки в Context экземпляре, хранящемся в нем Parser, и определение Parser структуры указывает, что время жизни ссылки Contextи время жизни строкового среза, которое Contextвыполняется, должно быть одинаковым.


// Для начала изменим имена времени жизни
struct Context<'s>(&'s str);

struct Parser<'c, 's> {
    context: &'c Context<'s>,
}

impl<'c, 's> Parser<'c, 's> {
    fn parse(&self) -> Result<(), &'s str> {
        Err(&self.context.0[1..])
    }
}

fn parse_context(context: Context) -> Result<(), &str> {
    Parser { context: &context }.parse()
}
// Rust не знает никаких отношений между 'c и 's. Чтобы быть в силе, ссылки на данные в течение Contextжизни 'sдолжны быть ограничены, чтобы гарантировать, что он живет дольше, чем ссылка со временем жизни 'c. Если значение 's не больше 'c, ссылка Context может быть недействительной.

// подтипирование времени жизни функции Rust указывает, что один параметр жизненного цикла живет как минимум до тех пор, пока жив другой.

// В нашем определении Parser, чтобы сказать, что 's(время жизни строкового фрагмента) гарантированно будет жить по крайней мере до тех пор, пока живет 'c(время жизни ссылки на Context)

struct Parser<'c, 's: 'c> {
    context: &'c Context<'s>,
}
// Теперь ссылка Context в Parser и ссылки на строки среза в Context имеют разные жизни; мы гарантировали, что время жизни среза строки 's больше, чем 'c ссылка на Context.
fn main(){}

Время жизни сокращение к меньшему

Более длительный срок службы может быть принужден к более короткому, так что он работает внутри области, в которой он обычно не работал. Это происходит в форме предполагаемого принуждения компилятором Rust, а также в виде объявления разницы в жизни:


//  имеет максимально короткое время жизни.
// Эти две ссылки затем принуждаются к этому времени жизни.
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
    first * second
}

// принуждаем время 'a укоротится до 'b
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first
}

fn main() {
    let first = 2; // 'a Longer lifetime
    {
        let second = 3; // 'b Shorter lifetime

        println!("The product is {}", multiply(&first, &second));
        println!("{} is the first", choose_first(&first, &second));
    };
}

Время жизни Ограничение его

T: 'a: Все ссылки в T должны пережить время жизни 'a

T: Trait + 'a: Тип T должен реализовать типаж Trait и все ссылки в T должны пережить 'a


use std::fmt::Debug;  

#[derive(Debug)]
struct Ref<'a, T: 'a>(&'a T);
// `Ref` содержит ссылки на обобщённый тип `T` который имеет неизвестное время жизни `'a`. 
// `T` ограничен так, что любые ссылки в `T` должны пережить `'a`.
// Кроме того, время жизни `Ref` не может превышать `'a`.

// Здесь приводится ссылка на `T`, где `T` реализует `Debug` и все ссылки в `T` переживут `'a`.
// К тому же, `'a` должен пережить функцию.
fn print_ref<'a, T:Debug + 'a>(t: &'a T) {
    println!("`print_ref`: t это {:?}", t);
}

fn main() {
    let x = 7;
    let ref_x = Ref(&x);

    print_ref(&ref_x);
    print(ref_x);
}

Возврат &str связываем время жизни возвращаемого значения с наименьшим временем жизни переданного параметра (время жизни ссылки, возвращаемой функцией, совпадает с наименьшим временем жизни ссылочных данных, переданных в нее)

lifetime-syntax

Для некоторого времени жизни 'a функция принимает два параметра, оба из которых являются строковыми срезами, которые живут, по меньшей мере, до тех пор, пока это не будет продолжаться 'a Подпись функции также сообщает Rust, что отрезок строки, возвращаемый функцией, будет работать, по крайней мере, столько же, сколько время жизни 'a. Эти ограничения - это то, что мы хотим, чтобы Rust обеспечивал соблюдение. Помните, что когда мы указываем параметры жизни в этой сигнатуре функции, мы не изменяем сроки жизни любых значений, переданных или возвращаемых. Скорее, мы указываем, что средство проверки займа должно отклонять любые значения, которые не соответствуют этим ограничениям. Обратите внимание, что longest функции не нужно точно знать, как долго x, y будет жить, только для того, чтобы какая-то область могла быть заменена 'a который удовлетворит эту подпись.

fn longest_(x: & str, y: & str) -> & str { // Ошибка времени жизни возвращаемых данных
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn longest<'a >(x: &'a str, y: &'a str) -> &'a str { // Верно , время жизни совпадает со временем вызывающих данных
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn main(){
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(),   string2);
    println!("The longest string is {}", result);
}

Возврат &str

В этом примере string1 действителен до конца внешней области действия, string2 действителен до конца внутренней области и result ссылается на то, что действует до конца внутренней области.

 fn longest<'a >(x: &'a str, y: &'a str) -> &'a str { // Верно , время жизни совпадает со временем вызывающих данных
        if x.len() > y.len() {
            x
        } else {
            y
        }
  }
fn main(){
   let string1 = String::from("long string is long");

   {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("{}", result);
   }
}

Возврат &str с ошибкой Ошибка показывает, что для того, чтобы result был действительным для println!, string2 должна быть действительна до конца внешней области. Так как время жизни одинаковое - берется наименьшее, то в print! данные string2 уже недействительны.

fn main(){
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("{}", result);
}

PhantomData для уточнения lifetime

struct Foo<'a> {
    x: &'a i32,
}
impl<'a> Foo<'a> {
    fn x(&self) -> &'a i32 { self.x }
}
struct Foo2<'a,T:'a> {
    x: &'a T,
}
fn main(){}

Цель состоит в том, что базовые данные действительны только для жизни 'a, поэтому Slice не должны переживать 'a.

Однако это намерение не выражено в коде, поскольку нет использования времени жизни, 'a, следовательно, неясно, к каким данным оно относится.

Мы можем исправить это, указав компилятору, чтобы действовать, как если Slice структура содержит ссылку &'a T с помощью PhantomData

use std::marker::PhantomData;
struct Foo3<'a, T: 'a> {
    start: *const T,
    end: *const T,
    phantom: PhantomData<&'a T>
}
fn main() {
    let x = &5; // то же самое, что и `let _y = 5; let y = &_y;`
    let f = Foo { x };
    println!("{}", f.x);

    let x:&i32 = &6;
    let x:&&str = &"str";
    let x:&Vec<i32>= &vec![1,2,3];
    let f = Foo2{ x };
    println!("{:?}", f.x);

    let vec = vec![1,2,3];
    let ptr = vec.as_ptr();
    Foo3 {
        start: ptr,
        end: unsafe { ptr.offset(vec.len() as isize) },
        phantom: PhantomData,
    };
}

Время жизни с именем «static»

'static приводится к lifetime аргумента

lifetime/static_lifetime

Оно обозначает, что что-то имеет время жизни, равное времени жизни всей программы. И занимает постоянно место !!! Потому что ссылка всегда действительна: строки располагаются в сегменте данных конечного двоичного файла

Способ создать lifetime static:

  • 1. Создание строкового литерала, имеющего тип &'static str

    
       fn main(){
        let x: &'static str = "Привет, мир.";
       }
     
  • 2. Создание константы с ключевым словом static.

fn main(){
    // i32 добавляется в сегмент данных двоичного файла, а `x` ссылается на него
    static FOO: i32 = 5;
    let x: &'static i32 = &FOO;
}
  • 3. 'static как часть ограничения типажа:
fn generic<T>(x: T) where T: 'static {}
fn main(){}

static NUM: i32 = 18;

// `'static` приводится к lifetime аргумента
fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM
}
fn main() {
    {
        // Создадим строковый литерал и выведем его:
        let static_string = "Я в неизменяемой памяти";
        println!("static_string: {}", static_string);

        // Когда `static_string` выходит из области видимости, ссылка
        // на неё больше не может быть использована, но данные остаются в бинарном файле !!!
    }
    {
        let lifetime_num = 9;

        // Приведём `NUM` ко времени жизни `lifetime_num`:
        let coerced_static = coerce_static(&lifetime_num);

        println!("coerced_static: {}", coerced_static);
    }
    
    println!("NUM: {} остаётся доступным!", NUM);
}

Время жизни с именем «static»

3. 'static как часть ограничения типажа:

fn generic<T>(x: T) where T: 'static {}


use std::fmt::Debug;
fn print_it( input: impl Debug + 'static ) {
    println!( "Переданное значение 'static равно: {:?}", input );
}
fn main() {
    // I владеемое и не имеет ссылок, следовательно является 'static:
    let i = 5;
    print_it(i);

    // Упс, &I имеет время жизни, ограниченное областью видимости main(), поэтому оно не 'static:
    //print_it(&i);
    
     let y:&'static str = "hello";
     print_it(y);
}

Lifetime bounds указывает время жизни для ссылки на общий тип

Границы продолжительности жизни. Параметры времени жизни в качестве ограничений для общих типов.

Ошибка Rust не знает, как долго T будет жить в Ref типе

struct Ref<'a, T>(&'a T);

Потому что T может быть любым типом, T может быть ссылкой или типом, который содержит одну или несколько ссылок, каждая из которых может иметь свои собственные времена жизни. Rust не может быть уверен, что она T будет жить так долго как живет 'a

T: 'a синтаксис указывает, что T может быть любым типом, но если он содержит какие-либо ссылки, ссылки должны жить как минимум до тех пор, пока живет 'a

struct Ref<'a, T: 'a>(&'a T);

Или 'static ограничение продолжительности жизни T. Это означает, что если T содержит какие-либо ссылки, они должны иметь время 'static жизни.

struct StaticRef<T: 'static>(&'static T);

Потому что 'static означает, что ссылка должна жить до тех пор, пока вся программа, тип, который не содержит ссылок, соответствует критериям всех ссылок, живущих до тех пор, пока вся программа (потому что ссылок нет). Для проверки заимствования, касающегося ссылок, которые живут достаточно долго, нет никакого реального различия между типом, у которого нет ссылок, и типом, который имеет ссылки, которые живут вечно: оба они одинаковы для определения того, имеет ли ссылка более короткое время жизни, чем то, что это относится к.

Вывод времени жизни объекта-объекта: позволяет компилятору вывести сроки жизни объекта-объекта и когда они должны быть указаны

Вывод времени жизни объекта

Тип, реализующий черту в объекте признаков, имеет собственную жизнь.


trait Red { }

struct Ball<'a> {
    diameter: &'a i32,
}

impl<'a> Red for Ball<'a> { }

fn main() {
    let num = 5;
    let obj = Box::new(Ball { diameter: &num }) as Box; // Box или Box
}

Этот код компилируется без каких-либо ошибок, хотя мы явно не аннотировали связанные с ним времена жизни obj. Этот код работает, потому что существуют правила для работы с объектами жизни и объектов:

Время жизни объекта-объекта по умолчанию 'static. С &'a Trait или &'a mut Trait, это время жизни объекта-объекта по умолчанию 'a. В одном T: 'a предложении используется время жизни объекта-объекта по умолчанию 'a. С несколькими статьями, как T: 'a, нет времени жизни по умолчанию; мы должны быть явными.

Когда мы должны быть явными, мы можем добавить привязку жизни к объекту признаков, например, Box<dyn Red> используя синтаксис, Box<dyn Red + 'static> или Box<dyn Red + 'a>, в зависимости от того, существует ли эта ссылка для всей программы или нет

Анонимный lifetime '_

struct StrWrap<'a>(&'a str);

fn foo<'a>(s: &'a str) -> StrWrap<'a> { StrWrap(s) }

// Анонимный lifetime  '_ для сокращенной записи
fn foo(s: &str) -> StrWrap<'_> { StrWrap(s) }
fn main(){}

lifetime '_ также работает в impl заголовках:

// полная запись
impl<'a> fmt::Debug for StrWrap<'a> {....}

// короткая запись
impl fmt::Debug for StrWrap<'_> {....}

Типаж Deref DerefMut

std::ops::DerefMut

std::ops::Deref

при использовании оператора «точка» вам не нужно беспокоиться о * разыменовании

deref--derefmut

Вот правило: если есть тип U, и он реализует Deref<Target=T>, значения &U будут автоматически преобразованы в &T, когда это необходимо.

Если T: Deref<Target = U>, то &T переходит в &U с помощью метода deref() (Аналогично, если T: DerefMut, то &mut T переходит в &mut U через deref_mut())

Deref разработан специально для интеллектуальных указателей (подсчет ссылок) его следует применять только для интеллектуальных указателей.

Приведение deref работает только со ссылками, поэтому оно не работает, когда мы действительно хотим передать право собственности

Он не работает с операндами +=

Приведение deref не работает в универсальных контекстах

Deref coercion (принуждении)

Если T реализует Deref<Target = U> и x является значением типа T, то:

1.*x (где T не является ни ссылкой, ни необработанным указателем) эквивалентно *Deref::deref(&x)/*DerefMut::deref_mut(&mut x)

2.Значения типа &T приводятся к значениям типа &U

3.T неявно реализует все методы типа U

Deref-принуждение - это удобство, которое Rust выполняет по аргументам функций и методов.

Deref coercion преобразует ссылку на тип, который реализует Deref ссылку на тип, который Deref может преобразовать в исходный тип.

Принудительное принуждение происходит автоматически, когда мы передаем ссылку на значение определенного типа в качестве аргумента функции или метода, которые не соответствуют типу параметра в определении функции или метода. Последовательность вызовов deref метода преобразует тип, который мы предоставили, в тип, который требуется параметру.


use std::ops::{Deref, DerefMut};
struct WrapDeref { value: T}
impl Deref for WrapDeref {
    type Target = T;
    fn deref(&self) -> &Self::Target { &self.value}
}
impl DerefMut for WrapDeref {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value }
}
fn generic_deref_mut>(value:&mut T) /*where T::Target:Display+Debug*/{
    (*value).push('z');
    let v:String=(*Deref::deref(&value)).to_string();
    let v:String=(*value).to_string();
    println!("{}",v);
}
fn generic_deref>(value:&T) {
    let v:String=(*Deref::deref(&value)).to_string();
    let v:String=(*value).to_string();
    println!("{}",v);
}
fn main(){
   let mut x:WrapDeref = WrapDeref { value: 'a' };
   *x = 'b'; // DerefMut
   assert_eq!('b', x.value);

   let mut x:WrapDeref = WrapDeref { value: "a".to_string() };
   (*x).push('b'); // DerefMut
   (*DerefMut::deref_mut(&mut x)).push('c');
   generic_deref_mut(&mut x);
   assert_eq!("abcz".to_string(), x.value);

   let x:WrapDeref = WrapDeref { value: "a".to_string() };
   assert_eq!("a".to_string(), *x); // Deref
   assert_eq!("a".to_string(), *Deref::deref(&x));
   generic_deref(&x);

   /*
    Deref coercion (принуждении)
    Если `T` реализует `Deref` и `x` является значением типа `T`, то:

    1.`*x` (где `T` не является ни ссылкой, ни необработанным указателем) эквивалентно `*Deref::deref(&x)`/`*DerefMut::deref_mut(&mut x)`
    2.Значения типа `&T` приводятся к значениям типа `&U`
    3.`T` неявно реализует все методы типа `U`
    */
    let mut x:WrapDeref = WrapDeref { value: "a".to_string() };
    assert_eq!(*x,"a".to_string());
    assert_eq!(*x,*Deref::deref(&x));// 1
    let s:&String = (&x);// 2
    let s:&mut String = (&mut x);// 2
    s.push_str("b");

    x.push_str("c");// 3
    let slice:&[u8]=x.as_bytes();// 3
    assert_eq!(slice,"abc".to_string().as_bytes());
}

Пример использования


fn foo_gen_type>(arg:R){
    //let v = &*arg;
    // или
    let v = *Deref::deref(&arg);
    println!("{:?}",v);
}
// или ----------------------------------------------
fn foo_impl_type>(arg:R){
    //let v = &*arg;
    // или
    let v = *Deref::deref(&arg);
    println!("{:?}",v);
}
// или ----------------------------------------------
struct Foo+Copy+Debug>{
    data:R
}
impl +Copy+Debug>Foo{
    fn new(data:R)->Self{
        Self{data}
    }
    fn show_deref(&self){
        println!("{:?}",self.data);
    }
}
fn main() {
    foo_gen_type::(&5);     
    foo_impl_type::<&i32>(&5);  
    let  f = Foo::::new(&5);
    f.show_deref();
    let data:i32 = *f.data;
}

Deref и DerefMut должен быть реализован только для типов интеллектуальных указателей. Самый распространенный способ, которым люди пытаются злоупотреблять этими трейтами, — это попытаться внедрить в Rust какое-то наследование данных в стиле ООП.

Если мы хотим, чтобы функциональность и поведение были похожи на Deref и DerefMut тогда-то, что мы на самом деле, вероятно, ищем, AsRef и AsMut

deref--derefmut

Кроме того, приведение deref не работает в универсальных контекстах.


use std::ops::Deref;
struct SortedVec(Vec);

impl SortedVec {
    fn new(mut vec: Vec) -> Self {
        vec.sort();
        SortedVec(vec)
    }
    fn push(&mut self, t: T) {
        self.0.push(t);
        self.0.sort();
    }
}
impl Deref for SortedVec {
    type Target = Vec;
    fn deref(&self) -> &Vec {
        &self.0
    }
}
// Очевидно, что мы не можем использовать DerefMut> здесь, иначе любой, кто использует, SortedVec сможет тривиально нарушить отсортированный порядок. 
impl DerefMut for SortedVec {
    fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
}
fn main() {
    let mut sorted = SortedVec::new(vec![2, 8, 6, 3]);
    sorted.push(1);
    // sorted[0]=2; // с DerefMut нарушить отсортированный порядок
    assert_eq!(&[1,2,3,6,8],sorted.0.as_slice());

    let mut sortedClone:Vec = sorted.clone();// метода clone нет в реализации SortedVec поэтому сработал Deref который вернул Vec
    sortedClone.push(4);// этот push не от SortedVec
    assert_eq!(&[1,2,3,6,8,4],sortedClone.as_slice());// не отсотрированно

    // p.s. можно обойти этот случай реализовав Clone для SortedVec
}

Пример

Реализация Deref для структуры Option и оболочки


use std::ops::Deref;
use std::fmt::Debug;

#[derive(Debug)]
struct Wrap(Option); // wrapper struct
impl Deref for Wrap {
    type Target = Option; // Our wrapper struct will coerce into Option
    fn deref(&self) -> &Option {
        &self.0 // We just extract the inner element
    }
}
impl Wrap {
    fn print_inner(&self) {
        println!("{:?}", self.0)
    }
}
fn fn_that_takes_option(x: &Option) {
    println!("{:?}", x)
}
fn main() {
    let x = Wrap(Some(1)); 
    println!("{}",x.is_some());
    println!("{:?}",x.map(|x| x + 1)); 
    fn_that_takes_option(&x); 
    x.print_inner() 
}

Пример

Использование Deref и AsRef для аргументов функции

// Для функций, которые необходимо взять набор объектов, срезы обычно являются хорошим выбором:
fn work_on_bytes(slice: &[u8]) {}
fn main(){
// Поскольку Vec<T> и массивы [T; N] реализовать Deref<Target=[T]> , их можно легко принудить к фрагменту:

    let vec = Vec::new();
    work_on_bytes(&vec);

    let arr = [0; 10];
    work_on_bytes(&arr);

    let slice = &[1,2,3];
    work_on_bytes(slice); // Note lack of &, since it doesn't need coercing
}

Однако вместо того, чтобы явно требовать срез, можно сделать функцию для принятия любого типа, который может использоваться как срез:

fn work_on_bytes<T: AsRef<[u8]>>(input: T) {
    let slice = input.as_ref();
}
// В этом примере функция work_on_bytes примет любой тип T который реализует as_ref() , который возвращает ссылку на [u8] .
fn main(){
    work_on_bytes(vec);
    work_on_bytes(arr);
    work_on_bytes(slice);
    work_on_bytes("strings work too!");
}

Типаж Drop

std::ops::Drop

std::mem::drop

trait.Drop

Типаж Drop имеет только один метод: drop, который вызывается автоматически, когда объект выходит из области видимости. Основное применение типажа Drop заключается в том, чтобы освободить ресурсы находящиеся в куче, которыми владеет экземпляр реализации

Box, Vec, String, File, и Process - это некоторые примеры типов, которые реализуют типаж Drop для освобождения ресурсов.

Для упаковки FFI-конструкций, которые нужно каким-то образом вернуть позже, также он используется для файлов, сокетов, дескрипторов баз данных

⚠️

file:///home/jeka/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/share/doc/rust/html/reference/destructors.html

destructors

Типаж Drop

освобождение памяти

Позволяет выполнить некоторый код, когда значение выходит из области видимости Часто Drop используют, чтобы освободить ресурсы, представленные структурой (struct). Например, счётчик ссылок Arc<T> уменьшает число активных ссылок в drop(), и когда оно достигает нуля, освобождает хранимое значение.

struct Firework {
    strength: i32,
}

impl Drop for Firework {
    fn drop(&mut self) {
        println!("БАБАХ силой {}!!!", self.strength);
    }
}

fn test() {
    let firecracker = Firework { strength: 1 };
    let tnt = Firework { strength: 100 };
}
fn main(){
    test();
    // БАБАХ силой 100!!!
    // БАБ
}

порядок уничтожения (drop) захваченных переменных внутри замыкания, созданного через move, не определён языком.

lukefleed

Если порядок уничтожения захваченных значений важен для логики программы, не полагайся на порядок drop замыкания.

Rust гарантирует порядок drop для обычных локальных переменных — в обратном порядке объявления (последний объявлен — первый уничтожен). Но для захваченных значений внутри замыкания такой порядок не стабилизирован (не определён спецификацией) и может зависеть от деталей реализации компилятора.

#![allow(dropping_copy_types)]

struct SomeTypeA(i32);
struct SomeTypeB(i32);
struct SomeTypeC(i32);

impl Drop for SomeTypeA {
    fn drop(&mut self) {
        println!("SomeTypeA");
    }
}
impl Drop for SomeTypeB {
    fn drop(&mut self) {
        println!("SomeTypeB");
    }
}
impl Drop for SomeTypeC {
    fn drop(&mut self) {
        println!("SomeTypeC");
    }
}
fn main() {
    let a = SomeTypeA(1);
    let b = SomeTypeB(2);
    let c = SomeTypeC(3);
    
    let cl = move || {
         
    };
    drop(cl);
}

Возможный вывод (пример):

SomeTypeB
SomeTypeA
SomeTypeC

На другой версии компилятора или с -O:

SomeTypeC
SomeTypeB
SomeTypeA

Нам нужно самим взять на себя реализацию порядка освобождения памяти:

let cl = move || {
    drop(a);
    drop(b);
    drop(c);
};

Либо гарантировать полями структуры.

struct Owned {
    a: SomeTypeA,
    b: SomeTypeB,
    c: SomeTypeC,
}
impl Drop for Owned {
    fn drop(&mut self) {
        println!("Owned drop start");
        // порядок контролируется полями структуры
    }
}
fn main() {
    let owned = Owned {
        a: SomeTypeA(1),
        b: SomeTypeB(2),
        c: SomeTypeC(3),
    };

    let cl = move || {
        let _ = &owned;
    };

    drop(cl);
}

Неявные оптимизации правил заимствования ссылок

Нарушение правил работы со ссылками, при одновременном существовании мутабельной &mut num и не мутабельной ссылки &mut

struct NumMutFef<'a>{
    num: &'a mut i32
}
 
fn main(){
    let mut num = 3;
    let num_mut_ref = NumMutFef{
        num: &mut num
    };
    println!("{}", &num);// ✅ OK
    // Но как такое может быть, если ссылка `&mut num` в NumMutFef живет до конца функции main и использование shared ссылки `&num` в `println!("{}", &num);` использует ее повторно! По идее компилятор должен запретить использовать нам shared ссылку, но так и будет если мы явно реализуем трейт Drop, что затрет оптимизацию компилятора с сокращенным временем жизни мутабельной ссылки до вызова shared ссылки.  
}

Теперь мы реализуем Drop, что расширит время жизни мутабельной ссылки, как это и должно быть до конца функции main

struct NumMutFef<'a>{
    num: &'a mut i32
}
impl Drop for NumMutFef<'_> {
    fn drop(&mut self) {}
}
fn main(){
    let mut num = 3;
    let num_mut_ref = NumMutFef{
        num: &mut num
    };
    println!("{}", &num);// ❌ ERROR
}

Оптимизация с сокращенным временем жизни, выглядит так:

struct NumMutFef<'a>{
    num: &'a mut i32
}
impl Drop for NumMutFef<'_> {
    fn drop(&mut self) {}
}
fn main(){
    let mut num = 3;
    {
        let num_mut_ref = NumMutFef{
            num: &mut num
        };
    }
    println!("{}", &num);// ✅ OK
}

Или явно вызвать drop, что явно сократит время жизни и компилятор разрешит использовать shared ссылку после drop

struct NumMutFef<'a>{
    num: &'a mut i32
}
impl Drop for NumMutFef<'_> {
    fn drop(&mut self) {
         
    }
}
fn main(){
    let mut num = 3;

    let num_mut_ref = NumMutFef{
        num: &mut num
    };
    drop(num_mut_ref);
    println!("{}", &num);
}

Утечка памяти

leaking

Однако мы должны быть осторожны с утечками деструкторов — это типы прокси. Это типы, которые управляют доступом к отдельному объекту, но фактически не владеют им. Прокси-объекты встречаются довольно редко. Прокси-объекты, о которых вам нужно будет позаботиться, встречаются еще реже. Однако мы сосредоточимся на трёх интересных примерах из стандартной библиотеки:

  • vec::Drain
  • Rc
  • thread::scoped::JoinGuard

Теперь рассмотрим Drain в середине итерации: некоторые значения были удалены, а другие — нет. Это означает, что часть Vec теперь заполнена логически неинициализированными данными!

fn main(){
   let mut vec = vec![Box::new(0); 4];

   {
      let mut drainer = vec.drain(..);

      // вытащите два элемента и сразу же бросьте их
      drainer.next();
      drainer.next();

      // избавиться от drainer, но не вызывать его деструктор
      mem::forget(drainer);
   }

    // Упс, vec[0] был отброшен, мы читаем указатель в освобожденную память!
    println!("{}", vec[0]);
}

Это явно не хорошо. К сожалению, мы как бы застряли между молотом и наковальней: поддержание согласованного состояния на каждом этапе требует огромных затрат (и сводит на нет все преимущества API). Неспособность поддерживать согласованное состояние приводит к неопределенному поведению в безопасном коде (что делает API ненадежным).

Так что мы можем сделать? Что ж, мы можем выбрать тривиально согласованное состояние: установить len Vec равным 0, когда мы начинаем итерацию, и при необходимости исправить это в деструкторе.

Классический пример — буферизованный вывод.

При закрытии потока нужно записать все данные, но нужно обработать ошибки. Не допустить вызова drop

Эмуляция Линейных Типов


use std::error::Error;
struct SafeBufWrite;
impl SafeBufWrite {
   fn flush(&mut self) -> Result<(),&'static dyn Error> { Ok(()) }
   fn close(mut self) -> Result<(),&'static dyn Error> {
      self.flush()?; // записать все данные
      std::mem::forget(self); // обезвредили drop (forget Вступает во владение и «забывает» о значении, не запуская его деструктор)
      Ok(())
   }
}
impl Drop for SafeBufWrite {
   fn drop(&mut self) {
      let _ = self.flush(); // игнорируем ошибки
      panic!("should be flushed explicitly")  // Если не вызвать метод close, при срабатывании drop будет ошибка
   }
}
fn main() {
   let buff = SafeBufWrite;
   let _ = buff.close();
}

Где применяют

Для структур содержащих raw pointer *mut, *const

для них Drop не будет отрабатывать автоматически

Реализация Drop по умолчанию для структур содержащих *mut не годится, поскольку единственное, что содержится в нашем Дереве, — это root: *mut Node, а Rust понятия не имеет, как его «отбросить». Если мы запустим наши тесты без явной реализации трейта Drop, будут утечки памяти.

impl Drop for Tree {
    fn drop(&mut self) {
        // Probably not the most efficient way to destroy the whole tree, but
        // it's simple and it works :)
        while !self.root.is_null() {
            self.remove_node(self.root);
        }
    }
}

Drop::drop() нельзя вызвать, но можно std::mem::drop

std::mem::drop принудительно сбросить значение до конца своей области видимости

Rust не позволяет вам вручную вызвать метод Drop признака drop; вместо этого вы должны вызвать std::mem::drop функцию, предоставляемую стандартной библиотекой, если вы хотите принудительно сбросить значение до конца своей области видимости. Вызов std::mem::drop для явного удаления значения до его выхода из области видимости


fn main() {
    let c = CustomSmartPointer { data: String::from("some data") };
    println!("CustomSmartPointer created.");
    std::mem::drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}

Где применяют

Для вложенных структур Drop будет отрабатывать, но не эффективно

может переполнить стек

third-drop

pub struct List<T> {
    head: Option<Box<Node<T>>>
}
struct Node<T> {
    elem: T,
    next: Option<Box<Node<T>>>,
}

list -> A -> B -> C

Когда list начнет выполнять Drop, он попытается drop A, который попытается drop B, который попытается drop C.
Это рекурсивный код, а рекурсивный код может взорвать стек! Нам придется вручную написать итеративный сброс для List подъема узлов из их коробок.

impl Drop for List {
    fn drop(&mut self) {
        let mut cur_link = mem::replace(&mut self.head, Link::Empty);
        // `while let` == "do this thing until this pattern doesn't match"
        while let Link::More(mut boxed_node) = cur_link {
            cur_link = mem::replace(&mut boxed_node.next, Link::Empty);
            // boxed_node goes out of scope and gets dropped here;
            // but its Node's `next` field has been set to Link::Empty
            // so no unbounded recursion occurs.
        }
    }
}

или

impl<T> Drop for List<T> {
    fn drop(&mut self) {
        let mut cur_link = self.head.take();
        while let Some(mut boxed_node) = cur_link {
            cur_link = boxed_node.next.take();
        }
    }
}

Наша реализация drop на самом деле очень похожа на while let Some(_) = self.pop() { }, которая, безусловно, проще. Pop возвращает Option<i32>, в то время как наша реализация манипулирует только Links ( Box<Node>). Таким образом, наша реализация перемещает только указатели на узлы, в то время как реализация на основе всплывающих окон перемещает значения, которые мы храним в узлах. Это может быть очень дорого, если мы обобщим наш список и кто-то будет использовать его для хранения больших экземпляров

Использование локальных данных после выхода их из области видимости


#[derive(Clone,Debug)] 
struct Foo(i32);
/*
impl Drop for Foo {
    fn drop(&mut self) {
        println!("exit");
    }
}
*/

// без необработанного указателя нельзя вернуть ссылку на локальную переменную
// таким образом мы обходим ограничение что-бы сработал деструктор локального обьекта
fn test_ptr() -> *mut Foo{
  let mut local_foo = Foo(2);
  let var_ref = &mut local_foo;  
  let var_ref_raw_ptr = var_ref as *mut Foo;
  unsafe{ 
    (*var_ref_raw_ptr).0+=1i32;
    println!("befor drop foo = {:?} addr:{:p}", *var_ref_raw_ptr,var_ref_raw_ptr);
  }
   var_ref_raw_ptr
}

fn main() {
 // При взятии необработанного указателя, на программиста возлагается ответственность освободить память 
 // Но так как мы закоментировали реализацию Drop для Foo то после выхода из области видимости локальной переменной local_foo
 // память не освобождается и мы можем использовать через ее адресс ее данные
  let ptr = test_ptr();
  unsafe{ 
   println!("after drop foo = {:?} addr:{:p}", *ptr,ptr);
   assert_eq!(3,(*ptr).0);
  }
  //println!("address = {:X}", ptr as usize);// 7FFC8F9C8FF0
  //println!("address = {:p}", ptr );// 0x7ffc8f9c8ff0

  let foo:&Foo = unsafe{ &*ptr };
  println!("foo = {:?}", foo );
  assert_eq!(3,(*foo).0); 
}

Как из &mut получить значение ? через обвертку из Option с подменой через take()

take это std::mem::replace(x, y::default()) получить T из &mut T

struct App(Option<Database>);
// struct App(Database);

struct Database;
impl Database {
    fn shutdown(self){ /*move*/}
}
impl App {
    fn drop(&mut self){
      // self.0.shutdown(); // не может выйти из заимствованного контента cannot move out of borrowed content
      let db = self.0.take().unwrap();
      assert!(self.0.is_none());
      db.shutdown();
    }
}
fn main(){}

реализация Drop


struct Droppable {
    name: &'static str,
}
// Это простая реализация `drop`, которая добавляет вывод в консоль.
impl Drop for Droppable {
    fn drop(&mut self) {
        println!("> Сбросили {}", self.name);
    }
}
fn main() {
    let _a = Droppable { name: "a" };
    // блок А
    {
        let _b = Droppable { name: "b" };
        // блок Б
        {
            let _c = Droppable { name: "c" };
            let _d = Droppable { name: "d" };
            println!("Выходим из блока Б");
        }
        println!("Вышли из блока Б");
        println!("Выходим из блока А");
    }
    println!("Вышли из блока А");

    // Переменную можно сбросить вручную с помощью функции `drop`.
    drop(_a);// переменная a удалится сдесь иначе после завершения программы
    // Попробуйте закомментировать эту строку

    println!("Конец главной функции.");

    // *Нельзя* сбросить `_a` снова, потому что переменная уже
    // (вручную) сброшена.
}

// $ RUST_BACKTRACE=short cargo run 

Присвоения типу полезного значения по умолчанию.

Создание значений по умолчанию

Обеспечение конструкторов по умолчанию

Если все поля структуры/перечисления реализуют Default, то макрос автоматически реализует Default для всей структуры/перечисления.

Если вы не хотите или не можете реализовать Default для всех полей, вы можете использовать Option<T> для некоторых из них и инициализировать их значением None.

Назначить трейт через derive для примитивного типа


#[derive(Default)]
struct Test{
    data:i32
}

или реализовать трейт Default

#[derive(PartialEq,Debug)]
struct Test{
    data:i32
}
impl std::default::Default for Test{
    fn default() -> Self{
          Self{data:0_i32}
    }
}
      
fn main() {
    let mut t:Test;
    t = Test::default();
    let t_2 = Test::default();

    let t:Test =  std::default::Default::default();
    assert_eq!( 0_i32, t.data );

    // ваш тип может использоваться там, где требуется Default реализация, 
    // любой из *or_default функций стандартной библиотеки 

    // Option::unwrap_or_default()
    let wrap:Option = None;
    let t: Test = wrap.unwrap_or_default();
    assert_eq!( 0_i32, t.data );

    // Result::unwrap_or_default()
    let wrap:Result = Err(());
    let t: Test = wrap.unwrap_or_default();
    assert_eq!( 0_i32, t.data );

    // HashMap::or_default()
    use std::collections::HashMap;
    let mut map: HashMap<&str, Test> = HashMap::new();
    let t:&mut Test = map.entry("poneyland").or_default();// entry - Получить состояние записи
    assert_eq!( *t, Test::default());
}
#[derive(SmartDefault)]
enum Foo {
    Bar,
    #[default]
    Baz {
        #[default = 12]
        a: i32,
        b: i32,
        #[default(Some(Default::default()))]
        c: Option<i32>,
        #[default(_code = "vec![1, 2, 3]")]
        d: Vec<u32>,
        #[default = "four"]
        e: String,
    },
    Qux(i32),
}
fn main(){}

Заполнитель

Создать структуру со значениями и заполнить остальные по умолчанию


fn main(){
  let x = Foo { bar: baz, ..Default::default() };
}

Для примитивов


fn main(){
 #[derive(Default)]
 struct SomeOptions {
    foo: i32,
    bar: f32,
 }
 let options: SomeOptions = Default::default();

 // Если вы хотите переопределить конкретный параметр, но сохраните остальные значения по умолчанию:
 let  options  =  SomeOptions { foo : 42 , ..Default::default()};

 let i: i8 = Default::default();
 let (x, y): (Option, f64) = Default::default();
 let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();
 println!("i:{},x:{:?},y:{},a:{},b:{},c:{},d:{}",i,x,y,a,b,c,d);// i:0,x:None,y:0,a:0,b:0,c:false,d:false
}

Своя реализация для сложного типа


 #[derive(Debug)]
 struct Item{
       data:i32
  }

 #[derive(Debug)]
 struct List{
      values:Vec
 }
    
 impl std::default::Default for List{
     fn default() -> List{
            List{values:Vec::new()}
           // или уже с данными List{values:vec!(Item{data:100})}
      }
 }

fn main(){
   let l: List = std::default::Default::default();
   println!("{:?}",l );// List { values: [] }
   // если с данными println!("{}",l.values[0].data); // 100
}

Еще своя реализация

enum Kind { A,  B,  C}

impl std::default::Default for Kind {
     fn default() -> Kind { Kind::A }
}
fn main(){
   let k: i8 = std::default::Default::default();
   println!("{:?}",k);// 0 так как в enum первый элемент это 0
}

#[derive(Default)]
enum Kind {
    #[default]
    A,
    B,
    C,
}
fn main(){}

Использование с дженериками


#[derive(Debug, Default)]
struct GenericStruct {
    value: T,
}

fn main() {
    // Инициализация структуры с использованием значения по умолчанию для типового параметра i32
    let gs: GenericStruct = Default::default();
    println!("{:?}", gs);
}

Module std::convert

Преобразование value в value осуществляется с помощью From и Into зеркально отраженных трейтов (реализация одного автоматически реализует другой).

Эти качества обеспечивают конверсию без ошибок

Все эти черты требуют владения переданным значением или ссылкой

Если конвертация может закончиться ошибкой, вам следует использовать TryFrom/TryInto аналоги, которые допускают отказ контролируемым образом

Применение Into

Следует избегать реализации Into и реализовывать From. Реализация From автоматически обеспечивает реализацию благодаря Into общей реализации в стандартной библиотеке.

Мы хотим, чтобы универсальная функция принимала все аргументы, которые могут быть преобразованы в указанный тип T

Пример: ф-ция принимает любой аргумент преобразующейся в Vec<u8> благодаря Into


fn is_hello>>(s: T) {
    let bytes = b"hello".to_vec();
    assert_eq!(bytes, s.into());
}
fn main(){
    // Into автореализация `impl Into> for String`
    let s = "hello".to_string();
    is_hello(s);
}

Если: From<T> for U "auto implies" => Into<U> for T

То для: impl From<&str> for String "auto implies" => Into<String> for &str


fn main(){
  let s:&str = "hello";
  let heap:String = s.into();// `Into for &str`
  let heap:String = String::from(s);// `impl From<&str> for String`

  // То для: `impl<'a> From for Cow<'a, str>` "auto implies" => `Into> for String`
  use std::borrow::Cow;
  let heap:String = "hello".to_string();
  let c:Cow<'_,str> = heap.into(); // `Into> for String`
  let s:String = c.into_owned();
}

into()

try_into()


fn main(){
 let num: u32 = 5;
 let big_num: u64 = num.into();
 let small_num: u16 = big_num.try_into().expect("Value is too big");
}

преимущество наличия Into реализации (не имеет значения, явная она или автоматическая) заключается в том, что мы можем использовать ее для расширения аргументов функции от определенного типа до любого типа, который можно преобразовать в целевой тип

Функция into определяется довольно просто: она забирает self (нечто, реализующее Into) и возвращает значение типа T. Вот пример того, как это можно использовать:

struct Token {
    raw: String,
}
impl Token {
    // Создание нового токена
    //
    // Может принимать как &str так и String
    pub fn new<S>(raw: S) -> Token where S: Into<String>{ 
         // Поскольку стандартная библиотека уже предоставляет Into<String> для &str и String
         // существует обобщенная реализация Into для всех типов, которые реализуют типаж From
        Token { raw: raw.into() }
    }
}
fn main(){
   // &str
   let token = Token::new("abc123");

   // String
   let token = Token::new(secret_from_vault("api.example.io"));
}

Преобразование from into и обратно

Реализация From<T> for U дает нам автоматически производную Into<U> for T реализацию


use std::convert::From;
#[derive(Debug)]
struct Number {
    value: i64
}
impl From for Number {
    fn from(item: i64) -> Self {
        Number { value: item }
    }
}
fn main() {
    let num = Number::from(30);
    //let num = Number{value:30};
    println!("My number is {:?}", num);

    let int = 5;
    let num: Number = int.into();
    println!("My number is {:?}", num);
}

TryFrom

что-то одно должно быть реализованно либо From либо TryFrom !

use std::convert::TryFrom;

struct SuperiorThanZero(i32);

impl TryFrom<i32> for SuperiorThanZero {
    type Error = &'static str;

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value < 0 {
            Err("SuperiorThanZero only accepts value superior than zero!")
        } else {
            Ok(SuperiorThanZero(value))
        }
    }
}
fn main(){
    if let Ok(superior) = SuperiorThanZero::try_from(44_i32){

    }
}
impl From<num::ParseFloatError> for CliError {
    fn from(err: num::ParseFloatError) -> CliError {
        CliError::ParseFloat(err)
    }
}

String implements From<&str>


fn main(){
   let string = "hello".to_string();
   let other_string = String::from("hello");

   assert_eq!(string, other_string);
}

Тип A преобразуем в тип B

Реализация From<T> for U дает нам автоматически производную Into<U> for T реализацию

use std::convert::From;
struct A{
   field_a:i32
}

#[derive(Debug)]
struct B{
   field_b:i32
}

impl From<A> for B {
    fn from(a: A) -> Self {
        B{field_b:a.field_a}
    }
}

fn main(){
    // `impl From for A`
    let a:A = A{field_a:8};
    let b:B = From::from(a);
    println!("{:?}",b);// B { field_b: 8 }

    // Into автореализация `impl Into<A> for B`
    let a:A = A{field_a:8};
    let b:B = a.into();
    println!("{:?}",b);// B { field_b: 8 }
}

Для Error

use std::io::{self, Read};
use std::num;

enum CliError {
    IoError(io::Error),
    ParseError(num::ParseIntError),
}
impl From<io::Error> for CliError {
    fn from(error: io::Error) -> Self {
        CliError::IoError(error)
    }
}
impl From<num::ParseIntError> for CliError {
    fn from(error: num::ParseIntError) -> Self {
        CliError::ParseError(error)
    }
}
fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> {
    let mut file = std::fs::File::open("test")?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let num: i32 = contents.trim().parse()?;
    Ok(num)
}
fn main(){}

Пример

Реализуя трейты мы можем вызывать, SortedVec::from() не заботясь о том, является ли аргумент срезом, Vec или LinkedList

convenient_and_idiomatic_conversions_in_rust

Реализация позволяет избежать клонирования вектора, и, на мой взгляд, скрытие любых промежуточных шагов приводит к более приятному API


/// Наша простая сортированная векторная структура - это просто оболочка вокруг Vec
struct SortedVec(Vec);

/// Ожидается преобразование фрагментов в SortedVec.
impl<'a, T: Ord + Clone> From<&'a [T]> for SortedVec {
    fn from(slice: &[T]) -> Self {
        let mut vec = slice.to_owned();
        vec.sort();
        SortedVec(vec)
    }
}

/// Также ожидается преобразование Vec.
/// Мы можем отсортировать вектор на месте, а затем поместить его в SortedVec.
impl From> for SortedVec {
    fn from(mut vec: Vec) -> Self {
        vec.sort();
        SortedVec(vec)
    }
}

/// Преобразование LinkedList также имеет смысл, но в нем нет 
/// представление среза, поэтому нам придется полагаться на его итератор.
impl From> for SortedVec {
    fn from(list: LinkedList) -> Self {
        let mut vec: Vec = list.iter().cloned().collect();
        vec.sort();
        SortedVec(vec)
    }
}
fn main(){
   let vec = vec![1u8, 2, 3];
   // Преобразовать в срез
   let sorted = SortedVec::from(&vec[1..]);
   // ... a vector
   let sorted = SortedVec::from(vec);
   // ... a linked list
   let mut linked_list: LinkedList = LinkedList::new();
   linked_list.extend(&[1, 2, 3]);
   let sorted = SortedVec::from(linked_list);
}

Пример

Нет никаких правил относительно того, когда, как или почему мы должны подразумевать From<T> наши типы, поэтому мы должны использовать свое лучшее суждение в каждой ситуации.

struct Point { x: i32, y: i32,}
impl Point {
    fn new(x: i32, y: i32) -> Point { Point { x, y }}
}
impl From<(i32, i32)> for Point {
    fn from((x, y): (i32, i32)) -> Point {  Point { x, y } }
}
struct Triangle {
    p1: Point,
    p2: Point,
    p3: Point,
}

impl Triangle {
    fn new(p1: Point, p2: Point, p3: Point) -> Triangle { 
        Triangle { p1, p2, p3 } 
    }
}

impl<P> From<[P; 3]> for Triangle where P: Into<Point> {
    fn from([p1, p2, p3]: [P; 3]) -> Triangle {
        Triangle {
            p1: p1.into(),
            p2: p2.into(),
            p3: p3.into(),
        }
    }
}
fn example() {
    // manual construction
    let triangle = Triangle {
        p1: Point { x: 0,  y: 0,},
        p2: Point { x: 1, y: 1, },
        p3: Point {  x: 2, y: 2,},
    };
    // using Point::new
    let triangle = Triangle {
        p1: Point::new(0, 0),
        p2: Point::new(1, 1),
        p3: Point::new(2, 2),
    };
    // using From<(i32, i32)> for Point
    let triangle = Triangle {
        p1: (0, 0).into(),
        p2: (1, 1).into(),
        p3: (2, 2).into(),
    };
    // using Triangle::new + From<(i32, i32)> for Point
    let triangle = Triangle::new(
        (0, 0).into(),
        (1, 1).into(),
        (2, 2).into(),
    );
    // using From<[Into<Point>; 3]> for Triangle
    let triangle: Triangle = [
        (0, 0),
        (1, 1),
        (2, 2),
    ].into();
}
fn main(){}

Пример: PacketType

enum PacketType {
    Data  = 0, // packet carries a data payload
    Fin   = 1, // signals the end of a connection
    State = 2, // signals acknowledgment of a packet
    Reset = 3, // forcibly terminates a connection
    Syn   = 4, // initiates a new connection with a peer
}

Учитывая это представление, как нам преобразовать в байтовое представление и обратно? Если бы мы следовали обычному стилю Rust и не присваивали вариантам никаких значений, числовое представление каждого варианта зависело бы от порядка их объявления, что может привести к ошибкам, если мы просто преобразуем варианты перечисления в числовые типы.

impl From<PacketType> for u8 {
    fn from(original: PacketType) -> u8 {
        match original {
            PacketType::Data  => 0,
            PacketType::Fin   => 1,
            PacketType::State => 2,
            PacketType::Reset => 3,
            PacketType::Syn   => 4,
        }
    }
}

Обратное преобразование из u8 в enum PacketType не верно, не для всех u8 есть аналог в enum PacketType Нам нужен способ сообщить, что преобразование не удалось, но вызов panic!() не является приемлемым вариантом.

impl TryFrom<u8> for PacketType {
    type Err = ParseError;
    fn try_from(original: u8) -> Result<Self, Self::Err> {
        match original {
            0 => Ok(PacketType::Data),
            1 => Ok(PacketType::Fin),
            2 => Ok(PacketType::State),
            3 => Ok(PacketType::Reset),
            4 => Ok(PacketType::Syn),
            n => Err(ParseError::InvalidPacketType(n))
        }
    }
}

Черта для абстрагирования идеи создания нового экземпляра типа из строки.

FromStr - преобразование из строки

FromStr не имеет lifetime, поэтому вы можете использовать типы только без ссылок

А если реализовать трейт Display то из объекта можно через .to_string() получить строку

trait.FromStr


use std::str::FromStr;
 
pub struct UserId(uuid::Uuid);
impl From for UserId {
    fn from(item: uuid::Uuid) -> Self {
        UserId(item)
    }
}
impl FromStr for UserId {
    type Err = uuid::Error;

    fn from_str(s: &str) -> Result {
        let n = s.parse::()?;
        Ok(UserId::from(n))// `impl From for UserId`
    }
}
fn main(){
    // `impl FromStr for UserId`
    let user = UserId::from_str("67e55044-10b1-426f-9247-bb680e5fe0c8").expect("Error parsing"); // явно
    let user = "67e55044-10b1-426f-9247-bb680e5fe0c8".parse::().expect("Error parsing"); // неявно

    // Into автореализация `impl Into for UserId`
    const ID: uuid::Uuid = uuid::uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8");
    let user:UserId = ID.into();
}
use std::str::FromStr;
use std::num::ParseIntError;

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32
}

impl FromStr for Point {
    type Err = ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let coords: Vec<&str> = s.trim_matches(|p| p == '(' || p == ')' ).split(",").collect();

        let x_fromstr = coords[0].parse::<i32>()?;
        let y_fromstr = coords[1].parse::<i32>()?;

        Ok(Point { x: x_fromstr, y: y_fromstr })
    }
}
fn main(){
    let p = Point::from_str("(1,2)");
    assert_eq!(p.unwrap(), Point{ x: 1, y: 2} );
}

1. через FromStr

use std::str::FromStr;
use std::num::ParseIntError;

#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum ErrorLevel2{
    DEBUG,INFO,WARN,ERROR,PANIC,EMPTY
}
impl FromStr for ErrorLevel2 {
    type Err = ParseIntError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
          match s{
           "debug" => Ok(ErrorLevel2::DEBUG ),
              "" => Ok(ErrorLevel2::EMPTY ),
              _ => Ok(ErrorLevel2::PANIC )
          }
    }
}
fn main(){
    let p:ErrorLevel2 = ErrorLevel2::from_str("debug").unwrap();
    print!("{:?}",p);
    assert_eq!(ErrorLevel2::DEBUG ,p);
}

2. через From


#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum ErrorLevel2{
    DEBUG,INFO,WARN,ERROR,PANIC,EMPTY
}
impl From<&'static str> for ErrorLevel2 {
    fn from(s: &'static str) -> Self {
        match s{
            "debug" => ErrorLevel2::DEBUG ,
            "" => ErrorLevel2::EMPTY ,
            _ => ErrorLevel2::PANIC
        }
    }
}
fn main(){
    let p:ErrorLevel2 = ErrorLevel2::from("debug");
    print!("{:?}",p);
    assert_eq!(ErrorLevel2::DEBUG ,p);
}

через FromStr

use safety_guard::safety;
use derive_more::{Display, FromStr};
/// Type of [`User`]'s unique number.
///
/// This type has no zero value. And it's values are always in range between
/// `1_000_000_000_000` and `9_999_999_999_999` inclusively.
#[derive(Debug)]
pub struct UserNum(u64);

/// Minimum allowed value of [`UserNum`].
pub const MIN_USER_NUM: u64 = 1_000_000_000_000;

/// Maximum allowed value of [`UserNum`].
pub const MAX_USER_NUM: u64 = 9_999_999_999_999;

impl UserNum {
    /// Creates new [`UserNum`] from given `u64` value if it meats [`UserNum`]
    /// invariants.
    #[inline]
    pub fn new(num: u64) -> Option<Self> {
        if Self::validate(num) {
            Some(Self(num))
        } else {
            None
        }
    }

    /// Creates new [`UserNum`] from given `u64` value without performing
    /// any validation.
    ///
    /// # Performance
    ///
    /// This function is especially useful when constructing [`UserNum`]
    /// values from some trusted source (like already validated numbers
    /// stored in database) as doesn't make any checks which are undesired
    /// in such context.
    #[inline]
    #[safety(
    assert(Self::validate(num)),
    "`num` must be in range [[`MIN_USER_NUM`], [`MAX_USER_NUM`]]"
    )]
    pub unsafe fn new_unchecked(num: u64) -> Self {
        Self(num)
    }

    /// Validates given `u64` to be a valid [`UserNum`].
    #[inline]
    pub fn validate(num: u64) -> bool {
        num >= MIN_USER_NUM && num <= MAX_USER_NUM
    }
}
impl From<UserNum> for u64 {
    #[inline]
    fn from(num: UserNum) -> Self {
        num.0
    }
}

impl str::FromStr for UserNum {
    type Err = UserNumParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let n = s.parse::<u64>().map_err(UserNumParseError::NotNumber)?;
        UserNum::new(n).ok_or_else(|| UserNumParseError::OutOfRange)
    }
}

/// Error of parsing [`UserNum`] from string.
#[derive(Debug, Display, Fail)]
pub enum UserNumParseError {
    /// String cannot be parsed as `u64` number.
    #[display(fmt = "Not a u64 number: {}", _0)]
    NotNumber(#[fail(cause)] std::num::ParseIntError),

    /// Number is not in the range required by [`UserNum`].
    #[display(
    fmt = "Number is not in range [{}, {}]",
    MIN_USER_NUM,
    MAX_USER_NUM
    )]
    OutOfRange,
}
fn main(){}

Методы - это функций реализации для enum, struct

  • self, (для значений в стеке)
  • &self, (для значений в куче, ссылка )
  • &mut self (для значений в куче, изменяемая ссылка )
  • Статический метод не принимает self

trait Say{
    fn say(&self) where Self: std::fmt::Debug{
        println!("say:{:?}",self);
    }
}
#[derive(Debug)]
struct MyStruct(i32);
impl Say for MyStruct{}

fn main(){
    let my = MyStruct(7);
    my.say(); // прямой вызов

    MyStruct::say(&my); // расширенный вариант вызова

    Say::say(&my); // вариант вызова через трейт

    ::say(&my); // расширенный вариант вызова через трейт
}

Rust не позволит иметь две мутабельные ссылки

pub struct MyStruct{
    pub a:i32,
    pub b:i32   
}

impl MyStruct{
    fn a_mut(&mut self) -> &mut i32{
        &mut self.a
    }
    fn b_mut(&mut self) -> &mut i32{
        &mut self.b
    }
}
fn main(){
    let mut my_struct = MyStruct{a:1, b:2};
    
// Тут нет ошибки так как мы берем ссылку на разеын данные, которые не пересекаются общим self
    let a = &mut my_struct.a;
    let b = &mut my_struct.b;
    assert_eq!(*a,1); // ✅ OK
    assert_eq!(*b,2);

// Тут есть ошибка так как мы возврашаем мутабельную ссылку через `&mut self` которая может быть только одна в этой области действия времени жизни
    let mut my_struct = MyStruct{a:1, b:2};
    let a = my_struct.a_mut();
    let b = my_struct.b_mut();
    assert_eq!(*a,1); // ❌ ERROR
    assert_eq!(*b,2);

// Для массива существует метод **split_at_mut** который позволит иметь две мутабельные ссылки на не пересекающиеся данные
}
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
} 
impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
    fn reference(&self) {
        println!("принимаем self по ссылке!");
    }
    fn mutable_reference(&mut self) {
        println!("принимаем self по изменяемой ссылке!");
    }
    fn takes_ownership(self) {
        println!("принимаем владение self!");
    }
    fn staticFn(){//static нет в аргументах self
        print!("hi");
    }
        fn staticFn2<T >(s:T)->T{//static
        s
    }
}
fn main(){
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };// Обращение к структуре выделить для нее память
    c.takes_ownership();// обращение к методу
    print!("{}",Circle::staticFn2("X"));// :: обращение к статическому методу
    println!("{}", c.area());
}

Обший impl для struct и enum


use core::fmt::Debug;
trait MyTrait{
    fn print(&self) where Self: Debug{
        println!("{:?}",&self);
    }
}
#[derive(Debug)]
struct A;
#[derive(Debug)]
enum B{
    a,b
}
impl MyTrait for A{}  
impl MyTrait for B{}

fn main() {
    let s = A;
    let e = B::b;
    s.print();
    e.print();
}

Для чего делать конкретную реализацию impl<type>

конкретная реализация обобщенного типа


use std::path::Path;
use std::fs::File;
struct CFile{
    file:T,
    name:String,
    count:i32
}
impl Drop for CFile {
    fn drop(&mut self) {
        //std::fs::remove_file(&self.name);
        println!("CFile is being dropped ");

    }
}
// красиво, тип T известен как File
impl CFile{
    fn new(name:&str)->CFile{
        let file = File::create(Path::new(name)).unwrap();
        CFile{
            file: file,count:1,name:name.to_string()
        }
    }
}
// Не красиво 
impl CFile {
    fn new2(file:T,name:&str)->Self{
        /*
            Если мы внутри создадим тип T то будет несовпадение типов
            так как у `CFile` неопрежеден T 
            let file = File::create(Path::new(name)).unwrap();
            Можем только снаружи задать тип T как File
        */
        CFile{
            file: file,count:1,name:name.to_string()
        }
    }
}
fn main(){
    let name  = "file.txt";
    // Не красиво
    let file = File::create(Path::new(name)).unwrap();
    let c:CFile =  CFile::new2( file,name);
     
    // Красиво
    let f = CFile::new( name);
}

trait impl и struct impl


trait FullName {
    fn full_name(&self) -> String;
}
struct Player {
    first_name: String,
    last_name: String,
}
impl FullName for Player {
    fn full_name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}
impl Player {
    fn pl_full_name(&self) -> String {
        format!("{} {}", self.first_name, self.last_name)
    }
}
fn main() {
    let player = Player {
        first_name: "Roger".to_string(),
        last_name: "Federer".to_string(),
    };

    println!("Player 02: {}", player.full_name());
    println!("Player 02: {}", player.pl_full_name());
}

Реализация обобщения для конкретного типа

pub struct Node<T> {
   pub data: T, 
}
impl <T>Node<T> {
    fn new(data:T) -> Self {
      Node {data }    
    }
}
impl <String> Node<String> {
    pub fn new_string(data: String) -> Self {
        Node { data: data.into() }
    }
}
fn main(){
   let node = Node::<i32>::new(123);
   let node = Node::<String>::new_string("Hello".into());
}

struct Generic<T,V> { data: T,val:V }

impl<T,V> Generic<T,V> {
     fn new(data: T,val:V)-> Generic<T,V> {
            //let res = data + val;// Ошибка у типов T,V нереализована операция +
            Generic { data: data,val: val}
     }
}
fn main(){
   let thing1 = Generic::new(0u32,1);
   let thing2 = Generic::new(8i32,1);
   let thing3 = Generic::new(3,5.5);
   println!("{}", thing3.val);
}

Шаблон «строитель» (Builder Pattern)

Rust не поддерживает перегрузку методов, именованные аргументы или переменное количество аргументов.

fn new() -> CircleBuilder {  // возврат себя
        CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
}
// и продолжаем работать с объектом возвращая self
fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
     self.x = coordinate;
     self
}
fn main(){}

Builder
    struct Circle {
        x: f64,
        y: f64,
        radius: f64,
    }
    impl Circle {
        fn area(&self) -> f64 {
            std::f64::consts::PI * (self.radius * self.radius)
        }
    }
    struct CircleBuilder {
        x: f64,
        y: f64,
        radius: f64,
    }
    impl CircleBuilder {
        fn new() -> CircleBuilder {
            CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
        }
        fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
            self.x = coordinate;
            self
        }
        fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
            self.y = coordinate;
            self
        }
        fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
            self.radius = radius;
            self
        }
        fn finalize(&self) -> Circle {
            Circle { x: self.x, y: self.y, radius: self.radius }
        }
    }
fn main(){
        let c = CircleBuilder::new()
            .x(1.0)
            .y(2.0)
            .radius(2.0)
            .finalize();

        println!("площадь: {}", c.area());
        println!("x: {}", c.x);
        println!("y: {}", c.y);
}

Утверждения where в impl

В этом примере impl не может быть непосредственно выражен без утверждения where:

trait WrapTrait:Debug{
    type Value;
    fn get_value(&self)->&Self::Value;
}

#[derive(Debug)]
struct Test<T>(T);

impl<T:Debug> WrapTrait for Test<T>{
    type Value = T;
    fn get_value(&self)->&T where T:Debug{
        &self.0
    }
}

// как выразить внутренний тип `Value` в сигнатуре ф-ции?
// Нам это нужно так как `get_value` возвращает внутренний тип `Value` и мы его печатаем

fn foo<T:WrapTrait>(t:&T) where T::Value: Debug {
    println!("Hello {:?}",t.get_value());
}
fn main(){}

use std::ops::{Add, Mul};
fn dot<T>(v1: &[T], v2: &[T]) -> T
    where T: Add<Output=T> + Mul<Output=T> + Default + Copy {
        let mut total = T::default();
        for i in 0 .. v1.len() {
               total = total + v1[i] * v2[i];
        }
        total
}
#[test]
fn test_dot() {
    assert_eq!(dot(&[1, 2, 3, 4], &[1, 1, 1, 1]), 10);
    assert_eq!(dot(&[53.0, 7.0], &[1.0, 5.0]), 88.0);
}
fn main(){}

обязательное утверждения where для ограничения типажом

// Вариант в стиле generic, но внутренний тип Item ожидает кокретный тип Sized а не трейт Ord
// fn find_max<I: Iterator<Item = Ord>>(iter: I) -> Option<I::Item>{ ❌

// с помощью where мы сможем указать ограничения трейтом
fn find_max<I>(iter: I) -> Option<I::Item>  ✅ 
  where I:Iterator,
             I::Item: Ord {    
    iter.reduce(|a, b| {
        if a >= b { a } else { b }
    })
}
fn main(){}

утверждения where могут применять ограничения типажей к произвольным типам, а не только к параметрам типа.


trait ConvertTo {
    fn convert(&self) -> Output;
}
impl ConvertTo for i32 {
    fn convert(&self) -> i64 { *self as i64 }
}
fn inverse() -> T where i32: ConvertTo { // использует ConvertTo как если бы это было «ConvertTo»
    1i32.convert()
}
fn main(){
    assert_eq!(1i64,inverse::());
}

Встроенные характеристики для бинарных операторов

КатегорияИмя характеристикиВыражениеЭквивалентное выражение
Арифметические операторыstd::ops::Addx + yx.add(y)
std::ops::Subx - yx.sub(y)
std::ops::Mulx * yx.mul(y)
std::ops::Divx / yx.div(y)
std::ops::Remx % yx.rem(y)
Поразрядные операторыstd::ops::BitAndx & yx.bitand(y)
std::ops::BitOrx | yx.bitor(y)
std::ops::BitXorx ^ yx.bitxor(y)
std::ops::Shlx << yx.shl(y)
std::ops::Shrx >> yx.shr(y)

Встроенные характеристики для унарных операторов

КатегорияИмя характеристикиВыражениеЭквивалентное выражение
Унарные операторыstd::ops::Neg-xx.neg()
std::ops::Not!xx.not()

crate derive_more

В Rust есть множество встроенных свойств, реализованных для его основных типов, таких как «Add», «Not», «From» или «Display».

Однако при обертывании этих типов внутри ваших собственных структур или перечислений вы теряете реализации этих свойств и должны создавать их заново.

Это особенно раздражает, когда ваши собственные структуры очень просты, например, при использовании часто рекомендуемого шаблона newtype (например, MyInt(i32)).


// [dependencies]
// derive_more = "1.0.0"

use derive_more::{Add, Display, From, Into};

#[derive(PartialEq, From, Add)]
struct MyInt(i32);

#[derive(PartialEq, From, Into)]
struct Point2D {
    x: i32,
    y: i32,
}

#[derive(PartialEq, From, Add, Display)]
enum MyEnum {
    #[display("int: {_0}")]
    Int(i32),
    Uint(u32),
    #[display("nothing")]
    Nothing,
}

fn main() {
    assert!(MyInt(11) == MyInt(5) + 6.into());
}

std::cmp::Ordering

Сравнение значений

Метод cmp есть у любого типа который можно сравнить

use std::cmp::Ordering;
use std::io;

fn main(){
     let mut guest = String::new();
     // guest.push_str("1");
     io::stdin().read_line(&mut guest).expect("Не удалось ввести данные");
     /* постоянный ввод
     loop {
        io::stdin().read_line(&mut guest).expect("Не удалось ввести данные");
        let guest:u32 = match guest.trim().parse(){
          Ok(num) => num,
            Err(_) => continue
        };
     }*/

     let number = "4".to_string();

     match guest.cmp(&number){
        Ordering::Less => print!("меньше"),
        Ordering::Greater => print!("больше"),
        Ordering::Equal => print!("равно")
     }
     let result = 1.cmp(&1);
     assert_eq!(Ordering::Equal, result);
}

std::ops::AddAssign

«Оператор-равно» теперь можно реализовать


use std::ops::AddAssign;
#[derive(Debug)]
struct Count { 
    value: i32,
}
impl AddAssign for Count {
    fn add_assign(&mut self, other: Count) {
        self.value += other.value;
    }
}
fn main() {
    let mut c1 = Count { value: 1 };
    let c2 = Count { value: 5 };
    c1 += c2;
    println!("{:?}", c1);// Это напечатает Count { value: 6 }.
}

Перегрузки определенных операций (+ * /) с помощью специальных типажей из модуля std::ops

std::ops

use std::ops::{Add,Sub,Mul};

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl  Add  for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point { x: self.x + other.x, y: self.y + other.y }
    }
}
impl  Sub  for Point {
    type Output = Point;
    fn sub(self, other: Point) -> Point {
        Point { x: self.x - other.x, y: self.y - other.y }
    }
}

impl  Mul  for Point {
    type Output = Point;
    fn mul(self, other: Point) -> Point {
        Point { x: self.x * other.x, y: self.y * other.y }
    }
}
fn main(){}

преобразования единиц путем реализации Add с параметром фантомного типа


use std::ops::Add;
use std::marker::PhantomData;

/// Создайте пустые перечисления для определения unit types.
#[derive(Debug, Clone, Copy)]
enum Inch {}
#[derive(Debug, Clone, Copy)]
enum Mm {}

/// `Length` это тип с параметром фантомного типа `T`,
/// и не является универсальным по типу длины (то есть `f64`).
///
/// `f64` уже реализует `Clone` и `Copy`.
#[derive(Debug, Clone, Copy)]
struct Length(f64, PhantomData);

/// The `Add` trait defines the behavior of the `+` operator.
impl Add for Length {
    type Output = Length;

    // add() returns a new `Length` struct containing the sum.
    fn add(self, rhs: Length) -> Length {
        // `+` calls the `Add` implementation for `f64`.
        Length(self.0 + rhs.0, PhantomData)
    }
}
fn main() {
    // Определяет one_foot, чтобы иметь параметр фантомного типа Inch.
    let one_foot:  Length = Length(12.0, PhantomData);
    // `one_meter` имеет параметр фантомного типа `Mm`.
    let one_meter: Length   = Length(1000.0, PhantomData);

    // Поскольку Length реализует Copy, add () не использует 
    // one_foot и one_meter, но копирует их в self и rhs.
    let two_feet = one_foot + one_foot;
    let two_meters = one_meter + one_meter;

    println!("one foot + one_foot = {:?} in", two_feet.0); // 24.0
    println!("one meter + one_meter = {:?} mm", two_meters.0); // 2000.0

    // Nonsensical operations fail as they should:
    // Compile-time Error: type mismatch.
    //let one_feter = one_foot + one_meter;
}

Использование типажей операций в обобщённых структурах

T также должен поддерживать копирование, чтобы Rust не пытался переместить self.side в возвращаемое значение


use std::ops::Mul;
trait HasArea {
    fn area(&self) -> T;
}

struct Square {
    x: T,  
    y: T,
    side: T,
}
// возвращаемое значение это реализация Mul умножения
impl HasArea for Square
    where T: Mul + Copy {
    
    fn area(&self) -> T {
        self.side * self.side
    }
}
fn main(){
 let s = Square {
    x: 0.0f64,
    y: 0.0f64,
    side: 12.0f64,
 };
 println!("Площадь s: {}", s.area());
}

Параметр типа фантома - это тот, который не отображается во время выполнения, но проверяется статически (и только) во время компиляции.

PhantomData представляет собой маркерную структуру нулевого размера, которую можно использовать для «пометки» содержащей структуру как имеющей определенные свойства.

Типы данных могут использовать дополнительные типовые параметры, чтобы действовать как маркеры или выполнять проверку типов во время компиляции. Эти дополнительные параметры не содержат значений хранения и не имеют поведения во время выполнения.

Это просто заставляет вас четко указывать на их отношение к остальному типу, что важно для таких вещей, как автоматически производные черты (особенно небезопасные).

Поскольку в Rust имеется богатая система типов, логика и семантика программирования в основном выражаются в типах, а не в данных/значениях, что известно как концепция «программирования с типами». Часто это приводит к ситуациям, когда вам необходимо выразить некоторые отношения типов, не имея значений этих типов. Вот тут-то и появляются фантомные типы: они несут некоторую семантику на уровне типа, инварианты которых проверяются компилятором и полностью компилируются во время выполнения.

Зачем существует PhantomData?

Управление временем жизни:

В Rust время жизни (lifetime) является важной частью системы типов, которая помогает гарантировать безопасность памяти. PhantomData позволяет указать, что тип обладает определенным временем жизни, даже если он не содержит никаких данных с этим временем жизни.

use std::marker::PhantomData;

struct MyStruct<'a, T> {
    data: i32,
    marker: PhantomData<&'a T>,
}

impl<'a, T> MyStruct<'a, T> {
    fn new(data: i32) -> Self {
        MyStruct {
            data,
            marker: PhantomData,
        }
    }
}
fn main(){}

Управление обобщенными типами (Generics):

В некоторых случаях, структуры могут быть обобщенными по типам, но не хранить значения этих типов. PhantomData позволяет явно указывать, что структура зависит от этих типов. Корректное поведение обобщенных типов: PhantomData позволяет обобщенным типам правильно вести себя в различных контекстах, даже если они не хранят значения своих параметров типов.

struct MyContainer<T> {
    marker: PhantomData<T>,
}

impl<T> MyContainer<T> {
    fn new() -> Self {
        MyContainer {
            marker: PhantomData,
        }
    }
}
fn main(){}

Прозрачность

Прозрачность PhantomData является прозрачным для Auto-traits (Send, Sync, Unpin, UnwindSafe, and RefUnwindSafe), что означает, например, что PhantomData<usize> есть Send и Sized, а не PhantomData<dyn Any> ни Send, ни Sized

struct Nonce<Of>(PhantomData<Of>, usize);

// This compiles OK, as `Nonce<()>` is `Send`.
let nonce: Nonce<()> = Nonce(PhantomData, 1);
thread::spawn(move || {
    println!("{nonce:?}");
});

// This doesn't compile, as `Nonce<Rc<()>>` is not `Send`.
let nonce: Nonce<Rc<()>> = Nonce(PhantomData, 2);
thread::spawn(move || {
    println!("{nonce:?}");
});
fn main(){
// This doesn't compile, as `dyn Any` is not `Sized`.
    let nonce: Nonce<dyn Any> = Nonce(PhantomData, 3);
}

Чтобы избежать таких проблем, давайте просто сформируем правильный тип внутри PhantomData, чтобы у нас всегда были нужные реализации Auto-traits (Send, Sync, Unpin, UnwindSafe, and RefUnwindSafe), несмотря на подставленный тип:

struct Nonce<Of: ?Sized>(PhantomData<AtomicPtr<Box<Of>>>, usize);

// This compiles OK now, despite `Rc<()>` is not `Send`.
let nonce: Nonce<Rc<()>> = Nonce(PhantomData, 2);
thread::spawn(move || {
    println!("{nonce:?}");
});
fn main(){
// This compiles OK now, as any `?Sized` type is allowed.
    let nonce: Nonce<dyn Any> = Nonce(PhantomData, 3);
}

Пример создание разных типов


use std::marker::PhantomData;

/// Создаём пустые перечисления для определения типов единиц измерения.
#[derive(PartialEq)]
enum Inch {} 

#[derive(PartialEq)]
enum Mm {}

#[derive(PartialEq)]
struct Length(f64, PhantomData);
 
fn main() {
    // Создание разных типов:
    // тип для Length где PhantomData захватывает тип Inch
    let one_foot: Length = Length::(12.0, PhantomData);
    let one_foot_2: Length = Length::(12.0, PhantomData);
    if one_foot == one_foot_2{}// одинаковые типы можно сравнивать

    // тип для Length где PhantomData захватывает тип Mm
    let one_meter: Length = Length::(1000.0, PhantomData);  
    let one_meter_2: Length = Length::(1000.0, PhantomData);
    if one_meter == one_meter_2{}// одинаковые типы можно сравнивать

    // error[E0308]: mismatched types
    //if one_foot == one_meter{}// разные типы нельзя сравнивать
}

Pattern Phantom type

rust-by-example/generics/phantom

Типы данных могут использовать дополнительные обобщённые типы в качестве параметров-маркеров или для выполнения проверки типов во время компиляции.

use std::marker::PhantomData;

// Фантомная кортежная структура, которая имеет обобщение `A` со скрытым параметром `B`.
#[derive(PartialEq)] // Разрешаем для данного типа сравнения.
struct PhantomTuple<A, B>(A,PhantomData<B>);

// Фантомная структура, которая имеет обобщение `A` со скрытым параметром `B`.
#[derive(PartialEq)] // Разрешаем для данного типа сравнения.
struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }

// Заметьте: память выделена для обобщённого типа `A`, но не для `B`.
// Следовательно, `B` не может быть использована в вычислениях.

fn main() {
    // Здесь `f32` и `f64` - скрытые параметры.
    // Тип PhantomTuple объявлен с `<char, f32>`.
    let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
    // Тип PhantomTuple объявлен с `<char, f64>`.
    let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData);

    // Тип определён как `<char, f32>`.
    let _struct1: PhantomStruct<char, f32> = PhantomStruct {
        first: 'Q',
        phantom: PhantomData,
    };
    // Тип определён как `<char, f64>`.
    let _struct2: PhantomStruct<char, f64> = PhantomStruct {
        first: 'Q',
        phantom: PhantomData,
    };
    
    // Ошибка времени компиляции! Типы не совпадают, так что сравнение не может быть произведено:
    //println!("_tuple1 == _tuple2 даёт в результате: {}",
    //          _tuple1 == _tuple2);
    
    // Ошибка времени компиляции! Типы не совпадают, так что сравнение не может быть произведено:
    //println!("_struct1 == _struct2 даёт в результате: {}",
    //          _struct1 == _struct2);
}

Применение PhantomData для разрешения использования объявленных ссылок для синтаксического анализатора

nomicon/phantom-data

При работе с небезопасным кодом мы часто можем оказаться в ситуации, когда типы или время жизни логически связаны со структурой, но фактически не являются частью поля структуры.

struct Iter<'a, T: 'a> {
    ptr: *const T,
    end: *const T,
}

Однако, поскольку 'a не используется в теле структуры, он неограничен . Из-за проблем, которые это исторически вызывало, неограниченное время жизни и типы запрещены в определениях структур. Поэтому мы должны как-то относиться к этим типам в теле.

Мы делаем это с помощью маркера особого типа PhantomData. PhantomData не занимает места, но моделирует поле заданного типа для статического анализа.

struct Iter<'a, T: 'a> {
    ptr: *const T,
    end: *const T,
    _marker: std::marker::PhantomData<&'a T>,
}

Другой важный пример - Vec, который (приблизительно) определяется следующим образом: Средство проверки освобождения Drop определит, что Vec<T> не владеет никакими значениями типа T

struct Vec<T> {
    data: *const T, // *const for variance!
    len: usize,
    cap: usize,
}

Чтобы сообщить Drop, что у нас есть собственные значения типа T и, следовательно, мы можем освободить некоторые T, когда сработает Drop::drop Мы должны добавить дополнительные PhantomData:

struct Vec<T> {
    data: *const T, // *const for variance!
    len: usize,
    cap: usize,
    _marker: std::marker::PhantomData<T>,
}

Пометим структуру как !Send !Sync

Первый вариант: громоздкий

struct MyStruct;
impl !Send for MyStruct {}
impl !Sync for MyStruct {}

Второй вариант: с лишними данными внутри структуры

// Если типы внутри структуры есть Send и Sync то и структура тоже.
struct MyStruct {
    // adds 8 bytes to every instance
    _not_send_or_sync: std::rc::Rc<()>,
}

Третий вариант: без лишних данных

// Можно использовать маркер PhantomData:
type NotSendOrSyncPhantom = std::marker::PhantomData<std::rc::Rc<()>>;// Rc это !Send !Sync
struct MyStruct {
    // не добавляет дополнительного размера экземплярам
    _not_send_or_sync: NotSendOrSyncPhantom,
}

Альтернативы PhantomData

Использование типажей и ассоциированных типов В некоторых случаях вместо PhantomData можно использовать типажи (traits) и ассоциированные типы для указания зависимостей между типами: Когда нужно указать зависимости между типами через типажи.


trait MyTrait {
    type Output;
    fn get_output(&self) -> Self::Output;
}

struct MyStruct;

impl MyTrait for MyStruct {
    type Output = i32;
    fn get_output(&self) -> i32 {
        42
    }
}

fn main() {
    let s = MyStruct;
    println!("{}", s.get_output());
}

использование времени жизни для связи со структурой

controlling-threads-using-phantomdata-in-rust

Цель состоит в том, что базовые данные действительны только для жизни 'a, поэтому Slice не должны переживать 'a. Однако это намерение не выражено в коде, поскольку нет использования времени жизни, 'a, следовательно, неясно, к каким данным оно относится. Мы можем исправить это, указав компилятор, чтобы действовать, как если Slice структура содержит ссылку &'a T


use std::marker::PhantomData;
struct Foo3<'a, T: 'a> {
    start: *const T,
    end: *const T,
    phantom: PhantomData<&'a T>,
}
fn main(){
 let vec = vec![1,2,3];
 let ptr = vec.as_ptr();
 Foo3 {
    start: ptr,
    end: unsafe { ptr.offset(vec.len() as isize) },
    phantom: PhantomData,
 };
}

пример использования PhantomData

маркер к какому типу данных привязана структура

Jekahome/typestates

// Иногда бывает, что у вас есть неиспользуемые параметры типа, которые указывают,
// к какому типу данных привязана структура, хотя эти данные фактически не найдены в самой структуре.

struct Post<S> {
    post_id: u64,
    user: User,
    title: String,
    body: String,
    state: PhantomData<S>
}

/// Состояния
struct New;
struct Unmoderated;

/// Вариант основан на преобразованим From and PhantomData

/// New -- Unmoderated
impl From<Post<New>> for Post<Unmoderated> {
    fn from(_val: Post<New>) -> Post<Unmoderated> {
        Post {
            post_id: _val.post_id,
            user: _val.user,
            title: _val.title,
            body: _val.body,
            state: PhantomData,
        }
    }
}

/// Create new Post
/// state New
fn new(user: User, title: String, body: String) -> Post<New> {
    let post: Post<New> = Post {
        post_id: 1u64,
        user: user,
        title: title, // String::from("title"),
        body: body,
        state: PhantomData,
    };
    post
}
fn publish(post: Post<New>) -> Post<Unmoderated> {
    println!("New -- \"publish()\" --> Unmoderated");
    post.into()
}
fn main() {
    let user = User {
        user_id: 1u64,
        full_name: String::from("Egor Egorov"),
        email: String::from("email@mail.ru"),
    };
    let post_new:Post<New> = new(user, String::from("title"), String::from("body"));
    let post_unmoderated:Post<Unmoderated> = publish(post_new);// переход в другое состояние
}

Использование такого PhantomData позволяет вам использовать определенный тип, не будучи частью Struct.

using-phantomdata-as-a-type-marker


use std::marker::PhantomData;
struct Authenticator {
    _marker: PhantomData<*const T>, // Использование `* const T` указывает на то, что мы не владеем T
}
impl Authenticator {
    fn new() -> Authenticator {
        Authenticator {
            _marker: PhantomData,
        }
    }
    fn auth(&self, id: i64) -> bool {
        T::get_instance(id).is_some()
    }
}
trait GetInstance {
    type Output; // Using nightly this could be defaulted to `Self`
    fn get_instance(id: i64) -> Option;
}

struct Foo;
impl GetInstance for Foo {
    type Output = Self; 
    fn get_instance(id: i64) -> Option {
        // Здесь вы можете сделать что-то вроде поиска в базе данных или что-то подобное
        if id == 1 {
            Some(Foo)
        } else {
            None
        }
    }
}

struct User;
impl GetInstance for User {
    type Output = Self;
    fn get_instance(id: i64) -> Option {
        // Здесь вы можете сделать что-то вроде поиска в базе данных или что-то подобное
        if id == 2 {
            Some(User)
        } else {
            None
        }
    }
}
fn main() {
    let user_auth = Authenticator::::new();
    let other_auth = Authenticator::::new();
    
    assert!(user_auth.auth(2));
    assert!(!user_auth.auth(1));
    
    assert!(other_auth.auth(1));
    assert!(!other_auth.auth(2));
}

Атрибуты (Условная компиляция)

conditional-compilation

effective-rust/features

tooling-directives/#cfg

чи можу я ввімкнути якось умовну компіляцію? Типу:

fn test() {
  #[cfg(my_flag)]
   call_my_cfg_fn();
} 
$ RUSTFLAGS='--cfg my_flag'

Атрибуты - это метаданные, применяемые к какому-либо модулю, контейнеру или их элементу.

  1. Атрибуты для Тестирования
  2. Атрибуты для Derive
  3. Атрибуты для Диагностики
  4. Атрибуты для Генерация кода
  5. Атрибуты для Ограничения
  6. Атрибуты для Система типов
  7. Атрибуты для Debugger

Зависимость не может полагаться на features только те что перечислены в Cargo.toml

Наличие большого количества независимых features потенциально приводит к комбинаторному взрыву различных конфигураций сборки.

Фраза "Features should be additive" (features должны быть добавочными) относится к принципу проектирования и использования features таким образом, чтобы они только добавляли новую функциональность, а не изменяли или удаляли существующую.

effective-rust/features

[dependencies]
somecrate = { version = "^0.3", features = ["featureA", "rand" ] }

Эта строка гарантирует, что somecrate будет построен как с включенной функцией FeatureA, так и с функцией rand. Однако это могут быть не единственные включенные функции; другие функции также могут быть включены из-за явления, известного как унификация функций.

Это означает, что крейт будет построен с объединением всех функций, которые запрашиваются чем-либо в графе сборки. Другими словами, если какая-то другая зависимость в графе сборки также зависит от somecrate, но с включенной только функцией B, то крейт будет построен со всеми включенными функциями FeatureA, FeatureB и rand, чтобы удовлетворить всех.

То же самое относится и к функции по умолчанию: если ваш крейт устанавливает default-features = false для зависимости, но какое-то другое место в графе сборки оставляет функции по умолчанию включенными, то они будут включены.

Унификация features означает, что features должны только добавлять возможности но не отнимать; иметь взаимно совместимые функции — плохая идея, поскольку нет ничего, что могло бы помешать одновременному включению несовместимых функций разными пользователями.

Следует избегать ограничения общедоступных полей в структурах:

/// A structure whose contents are public, so external users can construct
/// instances of it.
#[derive(Debug)]
pub struct ExposedStruct {
    pub data: Vec<u8>,

    /// Additional data that is required only when the `schema` feature
    /// is enabled.
    #[cfg(feature = "schema")]
    pub schema: String,
}
fn main(){
    let s = somecrate::ExposedStruct {
       data: vec![0x82, 0x01, 0x01],

       // Only populate the field if we've requested
       // activation of `somecrate/schema`.
       #[cfg(feature = "use_schema")]
       schema: "[int int]",
    };
}

код не компилируется, если этот код не активируется, some crate/schema но активируется какая-то другая транзитивная зависимость. Суть проблемы в том, что только ящик, в котором есть features = "schema", может проверить features; пользователь ящика не может определить, включен ли Cargo some crate/schema или нет.

Аналогичное соображение применимо к публичным Trait, предназначенным для использования за пределами контейнера, в котором они определены. Следует избегать методов ограничения features для общедоступных Trait

/// Trait for items that support CBOR serialization.
pub trait AsCbor: Sized {
    /// Convert the item into CBOR-serialized data.
    fn serialize(&self) -> Result<Vec<u8>, Error>;

    /// Create an instance of the item from CBOR-serialized data.
    fn deserialize(data: &[u8]) -> Result<Self, Error>;

    /// Return the schema corresponding to this item.
    #[cfg(feature = "schema")]
    fn cddl(&self) -> String;
}
fn main(){}

Атрибуты - это метаданные, применяемые к какому-либо модулю, контейнеру или их элементу.

Благодаря атрибутам можно:

  • задать условия компиляции кода
  • задать имя, версию и тип (библиотека или исполняемый файл) контейнера
  • отключить lints (предупреждения)
  • включить возможности компилятора (макросы, глобальный импорт и другое.)
  • связаться с внешней библиотекой
  • пометить функции как юнит тесты
  • пометить функции, которые будут частью бенчмарка
  • Когда атрибуты применяются ко всему контейнеру, их синтаксис будет #![crate_attribute], когда они применяются к модулю или элементу модуля, их синтаксис станет #[item_attribute] (обратите внимание на отсутствие !).

Атрибуты могут принимать аргументы с различным синтаксисом:

#[attribute = "value"]

#[attribute(key = "value")]

#[attribute(value)]

Атрибуты могут иметь несколько значений и могут быть разделены несколькими строками:

#[attribute(value, value2)]

#[attribute(value, value2, value3,value4, value5)]

Атрибуты, влияющие на весь ящик или приложение

Атрибуты, в первую очередь управляющие испускаемым кодом

1. Атрибуты для Тестирования

#[test]
#[ignore = "not yet implemented"]
fn mytest() {
    // тест игнорируется
}

#[test]
#[should_panic(expected = "values don't match")]
fn mytest() {// Атрибут should_panic заставляет тест проходить только в том случае, если он действительно паникует 
    assert_eq!(1, 2, "values don't match");
}

2. Атрибуты для Derive

Атрибут derive позволяет автоматически генерировать новые элементы для структур данных. Ниже приводится список выводимых типажей:

  • Типажи сравнения: Eq, PartialEq, Ord, PartialOrd

  • Clone, для создания T из &T с помощью копии.

  • Copy, чтобы создать тип семантикой копирования, вместо семантики перемещения.

  • Hash, чтобы вычислить хеш из &T.

  • Default, чтобы создать пустой экземпляр типа данных.

  • Zero, для создания нулевого экземпляра числового типа данных.

  • Debug, чтобы отформатировать значение с помощью {:?}.

#[derive(PartialEq, Clone)]
struct Foo<T> {
    a: i32,
    b: T,
}

Вы можете реализовать derive собственные трейты с помощью процедурных макросов:

proc_macro_derive производные макросы, которые позволяют предоставлять настраиваемые реализации для атрибута #[derive(Trait)]:

Идиоматично, proc_macro_derive следует использовать только для получения реализаций признаков

#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_: TokenStream) -> TokenStream {
    "impl Struct{ fn answer() -> u32 { 42 } }".parse().unwrap()
}
#[derive(AnswerFn)]
struct Struct;
fn main(){}

3. Атрибуты для Диагностики

lint-levels

Используются для управления или генерации диагностических сообщений во время компиляции.

allow(C) отменяет проверку, C чтобы о нарушениях не сообщалось,

warn(C) предупреждает о нарушениях, C но продолжает компиляцию.

deny(C) сигнализирует об ошибке после обнаружения нарушения C

forbid(C) то же самое, что и deny(C), но также запрещает последующее изменение уровня Lint check

Им соответствуют флаги при компиляции

The -A, -W, -D, and -F

$ rustc lib.rs --crate-type=lib -D missing-docs -A unused-variables

3. Атрибуты для Диагностики

allow(C) - разрешить код (в случае dead_code разрешить неиспользуемый код)


#[allow(dead_code)]
fn typed_example(){}


// установить всю группу `pedantic` clippy lint для предупреждения
#![warn(clippy::pedantic)]
// не сигнализировать warnings from the `filter_map` clippy lint
#![allow(clippy::filter_map)]

fn main() {
    // ...
}

// не сигнализировать the `cmp_nan` clippy lint just for this function
#[allow(clippy::cmp_nan)]
fn foo() {
    // ...
}

3. Атрибуты для Диагностики

#[allow(non_camel_case_types)]

// Используйте этот атрибут, чтобы не выводить предупреждение
// о именах не в стиле CamelCase для псемдонимов типов
#[allow(non_camel_case_types)]
type u64_t = u64; 

3. Атрибуты для Диагностики

#![allow(dead_code)]

Атрибут, который убирает предупреждения компилятора о неиспользуемом коде.

В начале файла.

В продакшн коде его удалить надо, а в dev можно ставить


fn used_function() {}

// `#[allow(dead_code)]` - атрибут, который убирает проверку на неиспользуемый код
#[allow(dead_code)]
fn unused_function() {}

fn noisy_unused_function() {}
// ИСПРАВЬТЕ ^ Добавьте атрибут `dead_code`, чтобы убрать предупреждение

fn main() {
    used_function();
}

3. Атрибуты для Диагностики

warn(C) - предупреждает о нарушениях, C но продолжает компиляцию.

fn typed_example2(){
        #[allow(unused_variables)] // не выдавать предупреждение
         let x = 5;
         
         let y = 5;
         
          #[warn(unused_variables)]   // выдавать предупреждение
         let z = 5;
}
 /*
     warning: unused variable: `y`
      --> src/main.rs:17:10
       |
    17 |      let y = 5;
       |          ^ help: consider prefixing with an underscore: `_y`
       |
       = note: #[warn(unused_variables)] on by default
       
       warning: unused variable: `z`
      --> src/main.rs:20:10
       |
    20 |      let z = 5;
       |          ^ help: consider prefixing with an underscore: `_z`
 */
fn main(){}

3. Атрибуты для Диагностики

deny(C) - сигнализирует об ошибке после обнаружения нарушения C

   #![deny(
        missing_debug_implementations,
        nonstandard_style,
        rust_2018_idioms,
        trivial_casts,
        trivial_numeric_casts,
        unsafe_code
    )]

3. Атрибуты для Диагностики

forbid(C) - «Запретить» - это специальный уровень, который сильнее, чем «deny»

tooling-directives

3. Атрибуты для Диагностики

deprecated()

attributes/diagnostics

❗Атрибут deprecated помечает элемент как устаревший


#[deprecated(since = "5.2.0", note = "foo was rarely used. Users should instead use bar")]
pub fn foo() {}

pub fn bar() {}

3. Атрибуты для Диагностики

must_use

attributes/diagnostics

Атрибут используется для выдачи диагностического предупреждения must_use, когда значение не используется.

#[must_use]
struct MustUse {
    // some fields
}
fn main(){
// Нарушает правило `unused_must_use`  
    MustUse::new();
}

4. Атрибуты для Генерация кода

inlining

inline-in-rust

Атрибуты являются только подсказками и могут быть проигнорированы.

#[inline] - встроить ф-цию в месте ее вызова что бы улучшить скорость работы

#[target_feature(enable = "avx2")] - включить генерацию кода этой функции для определенных функций архитектуры платформы

#[inline] следует предпочесть использовать только для вещей, критически важных для производительности; например, использование #[inline] большинства функций, выполняющих ввод-вывод, будет абсолютно бессмысленным для производительности во время выполнения (и просто потеряет производительность во время компиляции).

Если вам действительно нужен самый быстрый двоичный файл, который может создать компилятор, вы можете/должны использовать оптимизацию времени компоновки ( rustc -C lto), которая имеет доступ ко всем зависимостям Rust ящика во встроенной форме (включая вещи без #[inline] атрибутов)

Почему инлайнинг имеет значение?

Вызов функции — это самый дорогой прыжок. Нужно сохранить адрес возврата (Return Address), очистить/сохранить регистры и, возможно, выделить еще память в стеке под локальные данные в функции.

Почему программисты используют inline функции? — они просят компилятор не делать «телепортацию», а просто вставить код функции прямо в место вызова, превращая это в обычную линейную последовательность команд без всяких стеков. Но у него есть обратная сторона: если функция огромная и мы «инлайним» её в 100 местах, размер нашего исполняемого файла (.exe или .bin) раздувается. Это может привести к тому, что код перестанет влезать в L1-кэш инструкций процессора, и программа, как ни парадоксально, станет работать медленнее. Поэтому компиляторы иногда игнорируют просьбу об inline, если видят, что это навредит.

Встраивание — это оптимизирующее преобразование, которое заменяет вызов функции ее телом.

Чтобы привести тривиальный пример, во время компиляции компилятор может преобразовать этот код:

fn f(w: u32) -> u32 {
    inline_me(w, 2)
}
fn inline_me(x: u32, y: u32) -> u32 {
    x * y
}

// В этот код:

fn f(w: u32) -> u32 {
    w * 2
}
fn main(){}

В Rust единицей (отдельной) компиляции является крейт. Если функция f определена в крейте A, то все вызовы f изнутри A могут быть встроены, так как компилятор имеет полный доступ к f. Однако, если f вызывается из какого-либо нижестоящего крейта B, такие вызовы не могут быть встроенными. Bы имеете доступ только к подписи f, а не к его телу.

4. Атрибуты для Генерация кода

track_caller

Применение атрибута к функции f позволяет коду внутри f получить подсказку о Location «самом верхнем» отслеживаемом вызове, который привел к fвызову. В точке наблюдения реализация ведет себя так, как будто она поднимается по стеку от f кадра, чтобы найти ближайший кадр функции без атрибутов outer, и возвращает Location отслеживаемый вызов в outer


#[track_caller]
fn f() {
    println!("{}", std::panic::Location::caller());
}

fn f2() {
    f();
}
 
fn main(){
  f2();
}

5. Атрибуты для Ограничения

attributes/limits

Атрибут recursion_limit может применяться на уровне ящика, чтобы установить максимальную глубину для потенциально бесконечно рекурсивных операций времени компиляции

#![recursion_limit = "4"]

macro_rules! a {
    () => { a!(1); };
    (1) => { a!(2); };
    (2) => { a!(3); };
    (3) => { a!(4); };
    (4) => { };
}
fn main(){
// Это не удается расширить, поскольку требуется глубина рекурсии больше 4.
    a!{}
    #![recursion_limit = "1"]

// Это не удается, поскольку для автоматического разыменования требуются два рекурсивных шага.
    (|_: &u8| {})(&&&1);
}

#![type_length_limit = "4"]

fn f<T>(x: T) {}

fn main(){
// Компиляция не удаётся, поскольку для мономорфизации в `f::<((((i32,), i32), i32), i32)>` требуется более 4 элементов типа.
    f(((((1,), 2), 3), 4));
}

6. Атрибуты для Система типов

Атрибут #[non_exhaustive] в Rust используется для того, чтобы предотвратить исчерпывающее (exhaustive) сопоставление с образцом (matching) в других модулях.

Это значит, что при использовании этого атрибута другие модули не смогут предположить, что они знают все варианты перечисления (enum) или все поля структуры, даже если они перечислены.

Это позволяет разработчикам библиотеки добавлять новые варианты перечислений или новые поля структур в будущем без нарушения обратной совместимости.

2_idioms/2_5_exhaustivity

the-non_exhaustive-attribute

2008-non-exhaustive

using-non_exhaustive-for-non-exhaustive-rust-structs

Для библиотек

Следующие атрибуты используются для изменения того, как можно использовать тип.

Атрибут non_exhaustive_

Атрибут указывает, что в будущем к типу или варианту может быть добавлено больше полей или вариантов non_exhaustive

#[non_exhaustive]
pub enum Error {
    Message(String),
    Other,
}

pub enum Message {
    #[non_exhaustive] Send { from: u32, to: u32, contents: String },
    #[non_exhaustive] Reaction(u32),
    #[non_exhaustive] Quit,
}
fn main(){}

7. Атрибуты для Debugger

#![debugger_visualizer(natvis_file = "Rectangle.natvis")]

// Этот контейнер - библиотека
#![crate_type = "lib"]

// Эта библиотека называется "rary"
#![crate_name = "rary"]

Взаимоисключающие функции

В редких случаях функции могут быть несовместимы друг с другом. Добавления ошибки компиляции для обнаружения этого сценария.

#[cfg(all(feature = "foo", feature = "bar"))]
compile_error!("feature \"foo\" and feature \"bar\" cannot be enabled at the same time");

Условная компиляция возможна благодаря двум операторам

Атрибуту  #[cfg(...)], который указывается на месте атрибута
Макросу  cfg!(...), который можно использовать в условных выражениях

// Эта функция будет скомпилирована только в том случае, если целевая ОС будет linux
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
    println!("Вы работаете в linux!");
}

// А эта функция будет скомпилирована, если целевая ОС *не* linux
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
    println!("Вы работаете *не* в linux!");
}

fn main() {
    are_you_on_linux();
    
    println!("Вы уверены?");
    if cfg!(target_os = "linux") {
        println!("Да. Это точно linux!");
    } else {
        println!("Да. Это точно *не* linux!");
    }
}
для всего файла
#![cfg(feature = "test")]

или для модуля
#[cfg(feature = "test")]
mod test{
...

Включить Debug только при тестировании

#[cfg_attr(test, derive(Debug))]

#[cfg_attr]

#[cfg]

cfg!

tooling-directives

#[cfg_attr]

Когда предикат конфигурации истинен, этот атрибут расширяется до атрибутов, перечисленных после предиката. Например, следующий модуль будет либо найден в целевом объекте, linux.rs либо windows.rs основан на нем.

#[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(windows, path = "windows.rs")]
mod os;

Могут быть указаны ноль, один или несколько атрибутов. Каждый из нескольких атрибутов будет преобразован в отдельные атрибуты. Например:
#[cfg_attr(feature = "magic", sparkles, crackles)]
fn bewitched() {}

// Когда включен флаг функции `magic`, приведенное выше будет расширяться до:
#[sparkles]
#[crackles]
fn bewitched() {}

#[cfg]

Если предикат истинен, вещь переписывается так, чтобы на ней не было cfg атрибута. Если предикат ложен, вещь удаляется из исходного кода.

#[cfg(target_os = "macos")]
fn macos_only() {
  // ...
}

// макрос cfg!: cfg!(...), который можно использовать в условных выражениях
if cfg!(target_os = "linux") {
    println!("Да. Это точно linux!");
} else {
    println!("Да. Это точно *не* linux!");
}

Пример, компиляция исходника в зависимости от аттрибута

Cargo.toml:
[features]
qa_build = []
extern crate qa_ex;

#[cfg(feature = "qa_build")]
use  qa_ex::something;

#[cfg(not(feature = "qa_build"))]
fn something(){
    println!("NOT");
}
fn main() {
    something();
} 
cargo build
./target/release/example # NOT

cargo build  --features qa_build
./target/release/example # вариант из crate

Собственные условия

how-do-i-use-conditional-compilation-with-cfg-and-cargo

Cargo.toml:

[package]
name = "adder"
version = "0.1.0"
edition = "2021"
 
[features]
default = ["others"]
some_condition = ["rand"]
others = []

[dependencies.rand]
version = "0.5"
optional = true

В качестве альтернативы Cargo.toml можно в .cargo/config.toml:

[build]
rustflags = "--cfg some_condition"

main.rs:


#[cfg(feature="some_condition")]
fn conditional_function() {
    let x = rand::random::();
    println!("condition met! Use rand:{}", x);
}

#[cfg(not(feature="some_condition"))]
fn conditional_function() {
    println!("condition not! Without rand :(");
}

fn main() {
    conditional_function();
}

$ cargo run --features some_condition

$ cargo build --features some_condition && ./target/debug/adder
$ cargo build && ./target/debug/adder

#[cfg_attr]

rust-cfg_attr

Cargo.toml:

[features]
test = []  В Cargo не обязательно заносить запуск и так отработает если передать флаг --features

#[cfg(feature = "test")]
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug)]
#[cfg_attr(feature = "test", derive(PartialEq, Serialize))]
pub struct UserEmail(String);

#[cfg(feature = "test")]
impl<'de> Deserialize<'de> for UserEmail {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let email = String::deserialize(deserializer)?;

        UserEmail::new(email).ok_or_else(|| {
            serde::de::Error::custom("`email` is not valid".to_string())
        })
    }
}
fn main(){}

Запуск:

cargo test  --features "test"

run-additional-tests-by-using-a-feature-flag-to-cargo-test

run-additional-tests-by-using-a-feature-flag-to-cargo-test

Without a workspace

Cargo.toml:

[package]
name = "feature-tests"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[features]
network = []
filesystem = []

[dependencies]
src/lib.rs
#[test]
#[cfg_attr(not(feature = "network"), ignore)]
fn network() {
    panic!("Touched the network");
}

#[test]
#[cfg_attr(not(feature = "filesystem"), ignore)]
fn filesystem() {
    panic!("Touched the filesystem");
}

Output:

$ cargo test

running 2 tests
test filesystem ... ignored
test network ... ignored

$ cargo test --features network

running 2 tests
test filesystem ... ignored
test network ... FAILED

$ cargo test --features filesystem

running 2 tests
test network ... ignored
test filesystem ... FAILED
(some output removed to better show effects)

With a workspace

Layout:

.
├── Cargo.toml
├── feature-tests
│   ├── Cargo.toml
│   ├── src
│   │   └── lib.rs
├── src
│   └── lib.rs
feature-tests contains the files from the first section above.

Cargo.toml:

[package]
name = "workspace"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[features]
filesystem = ["feature-tests/filesystem"]
network = ["feature-tests/network"]

[workspace]

[dependencies]
feature-tests = { path = "feature-tests" }

Output:

$ cargo test --all

running 2 tests
test filesystem ... ignored
test network ... ignored

$ cargo test --all --features=network

running 2 tests
test filesystem ... ignored
test network ... FAILED
(some output removed to better show effects)

Этот модуль содержит функции для запроса размера и выравнивания типов, инициализации и управления памятью.

убедиться, что в нашей программе нет ошибок с памятью

rustc src/main.rs && valgrind ./main

std::mem::transmute

mem/fn.transmute

Переинтерпретирует биты значения одного типа как другой тип.

Memory leaks

Утечка памяти в Rust безопасна

Drop может быть пропущен компилятором

lukefleed

Функция std::mem::forget принимает значение в качестве владельца и не запускает его деструктор drop() для v. Rust просто «забывает» переменную, не освобождая ресурсы, которыми она владеет.

let v = vec![1, 2, 3];
std::mem::forget(v);

Причина в том, что в Rust «безопасный» означает «не может вызывать неопределенное поведение» . Утечка памяти — это расточительство, но она:

  • не повреждает память;
  • не создает висячие указатели;
  • не вызывает состояний гонки данных.

Программа, в которой происходит утечка памяти, содержит ошибку, но не является неопределенной.

Более того, утечки памяти могут возникать в безопасном коде без вызова метода forget. Простейший пример — цикл ссылок с Rc:

#![allow(unused)]
fn main() {
use std::cell::RefCell;
use std::rc::Rc;

struct Node {
    next: Option<Rc<RefCell<Node>>>,
}
fn create_cycle() {
    let a = Rc::new(RefCell::new(Node { next: None }));
    let b = Rc::new(RefCell::new(Node { next: Some(a.clone()) }));
    a.borrow_mut().next = Some(b.clone());
}
}

При create cycle возврате значения Rc переменные выходят из области видимости, но каждая из них имеет счетчик ссылок, равный 2, из-за циклического режима. Счетчик уменьшается до 1, а не до 0. Узлы никогда не освобождаются. Это безопасный код без unsafe блоков, но он приводит к утечкам памяти.

Поскольку утечки памяти возможны даже в безопасном коде, язык не может предполагать, что деструкторы всегда выполняются.

Rust гарантирует, что drop вызывается для всех переменных, если они выходят из области видимости.

Но есть исключения, где Rust больше не контролирует lifetime, например:

  • std::mem::forget
  • ManuallyDrop<T>
  • mem::transmute и FFI

Если твой небезопасный код рассчитывает, что drop будет вызван, (а на деле его пропускают) инварианты могут быть нарушены — например: не освободилась память, не закрылся файл, не отпущены блокировки.

Безопасные абстракции должны быть построены так, чтобы пропуск деструкторов не ломал инварианты.

std::mem::replace Переходит src в указанное dest, возвращая предыдущее dest значение.

std::mem::swap Меняет два значения местами в памяти, не деинициализируя ни одно из них.

std::mem::take Заменяется dest на значение по умолчанию T, возвращая предыдущее dest значение.

std::mem:swap<T>(x: &mut T, y:&mut T)


use std::mem;
fn main(){

   // mem::swap Меняет местами значения в двух изменяемых местах, не деинициализируя ни одно из них.
    let mut x = 5;
    let mut y = 42;

    mem::swap(&mut x, &mut y);

    assert_eq!(42, x);
    assert_eq!(5, y);

    //------------------------------------
    let mut a_1 = A{data:"a_1".to_string()};
    let mut a_2 = A{data:"a_2".to_string()};
    mem::swap(&mut a_1, &mut a_2);
    assert_eq!(a_1.data,"a_2".to_string());
    assert_eq!(a_2.data,"a_1".to_string());

   // mem::take Если вы хотите заменить значение по умолчанию или фиктивное значение
    let mut v: Vec = vec![1, 2];

    let old_v = mem::take(&mut v);
    assert_eq!(vec![1, 2], old_v);
    assert!(v.is_empty());

    //------------------------------------
    let mut a = A{data:"data".to_string()};
    let old_a = mem::take(&mut a);
    assert_eq!("data".to_string(), old_a.data);
    assert_eq!("Hello".to_string(), a.data);

   // mem::replace Если вы хотите поменять местами переданное значение, вернув старое значение

    let mut dest: Vec = vec![1, 2];

    let src = mem::replace(&mut dest, vec![3, 4, 5]);
    assert_eq!(vec![1, 2], src);
    assert_eq!(vec![3, 4, 5], dest);
}

struct A{
    data:String
}

impl std::default::Default for A{
    fn default() -> A{
        A{data:"Hello".to_string()}
    }
}

Адрес места в памяти


fn main(){
//адрес  места в памяти
    let x = &42;
    let address = format!("{:p}", x);
    println!("{}",address);

    struct Length(i32);
    let l = Length(42);
    impl fmt::Pointer for Length {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "{:p}", self as *const Length)
        }
    }
    println!("l is in memory here: {:p}", l);
}

Function std::mem::replace

mem/fn.replace

replace позволяет потреблять поле структуры, заменяя его другим значением.

mem::replace(dest: &mut T, src: T) -> T - Получить T из &mut T Перемещается src в ссылку dest, возвращая предыдущее dest значение.

Завладеть данными не используя clone

idioms/mem-replace

Средство проверки заимствований не позволит нам извлечь из name перечисления (потому что что- то должно быть там). Мы, конечно, могли бы .clone() назвать и поместить клон в наш MyEnum::B, но это будет экземпляр антипаттерна «Клон для проверки заимствований». В любом случае, мы можем избежать лишнего распределения, изменив e только изменяемое заимствование.


use std::fmt::Debug;
use std::default::Default;
use std::mem;

#[derive(Debug,PartialEq)]
enum MyEnum {
    A { name: String, x: u8 },
    B { name: String }
}

fn a_to_b(e: &mut MyEnum) {
    if let MyEnum::A{ name, x:_x @ 3...7 } = e {
        // это извлекает наше имя и вставляет вместо него пустую строку 
        // (обратите внимание, что пустые строки не выделяются). 
        // Затем создаем новый вариант перечисления (который будет 
        // присваивается `* e`)
        
        *e = MyEnum::B{ name: mem::take(name) }
        //*e = MyEnum::B{ name: mem::replace(name,Default::default()) }
    }
}
fn main() {
    let mut e = MyEnum::A{name:"foo".to_owned(),x:4};
    a_to_b(&mut e);
    assert_eq!(e,MyEnum::B{name:"foo".to_owned()});
    println!("{:?}",e);
}

Обойти двойное заимствование


use std::default::Default;
use std::mem;
use std::collections::HashSet;

#[derive(Debug,PartialEq)]
struct Names {
    exclusions: Vec,
    names: HashSet,
}
impl Names {
    //fn apply_exclusions(&mut self) {
    // error: for_each уже позаимствовал self
    //    self.exclusions.drain(..).for_each(|name| {
    //        self.names.remove(&name);
    //    })
    //}
     fn apply_exclusions(&mut self) {
        let mut names = mem::replace(&mut self.names, HashSet::new());
        self.exclusions.drain(..).for_each(|name| {
            names.remove(&name);
        });
        mem::replace(&mut self.names, names);
    }
}
fn main() {
    let mut e = Names{exclusions:vec!["Einar".to_owned(), "Olaf".to_owned(), "Harald".to_owned()],
    names: [ "Einar".to_owned(), "Olaf".to_owned(), "Harald".to_owned() ].iter().cloned().collect::>()};
   
    e.apply_exclusions();
    println!("{:?}",e);
}
use std::mem;
fn main(){
   let mut v: Vec<i32> = vec![1, 2];

   let old_v = mem::replace(&mut v, vec![3, 4, 5]);
   assert_eq!(2, old_v.len());
   assert_eq!(3, v.len());
}
struct Buffer<T> { buf: Vec<T> }

impl<T> Buffer<T> {
    fn get_and_reset(&mut self) -> Vec<T> {
/*      
        // error: cannot move out of dereference of `&mut`-pointer
        let buf = self.buf;
        self.buf = Vec::new();
        buf 
*/
       std::mem::replace(&mut self.buf, Vec::new())
    }
}

Function std::mem:forget

fn forget(x: T)

поглощает значение без вызова Drop

Эта функция потребляет переданное ей значение, а затем не запускает свой деструктор

Классический пример — буферизованный вывод.

При закрытии потока нужно записать все данные, но нужно обработать ошибки.

Не допустить вызова drop

Эмуляция Линейных Типов


use std::error::Error;
struct SafeBufWrite;
impl SafeBufWrite {
   fn flush(&mut self) -> Result<(),&'static dyn Error> { Ok(()) }
   fn close(mut self) -> Result<(),&'static dyn Error> {
      self.flush()?; // записать все данные
      std::mem::forget(self); // обезвредили drop (forget Вступает во владение и «забывает» о значении, не запуская его деструктор)
      Ok(())
   }
}
impl Drop for SafeBufWrite {
   fn drop(&mut self) {
      let _ = self.flush(); // игнорируем ошибки
      panic!("should be flushed explicitly")  // Если не вызвать метод close, при срабатывании drop будет ошибка
   }
}
fn main() {
   let buff = SafeBufWrite;
   let _ = buff.close();
}

Типажи PartialEq Eq Ord PartialOrd

std::cmp::PartialEq

std::cmp::Eq

std::cmp::Ord

std::cmp::PartialOrd

PartialEq operators: x==y, x!=y methods: x.eq(y) x.ne(y)

PartialOrd operators: <, <=, >, >= methods: partial_cmp() x.lt(&y) x.le(&y) x.gt(&y) x.ge(&y)

tour-of-rusts-standard-library-traits

trait.PartialEq

trait.PartialOrd

PartialEq Частичное равенство для типов, которые не имеют полного отношения эквивалентности ( == ). Например, в числах с плавающей запятой NaN != NaN, поэтому типы с плавающей запятой реализуются, PartialEq но не Eq

PartialOrd и Ord Упорядочивающие traits позволяют сравнивать ( <, >, <=, >=) два элемента одного типа, возвращая Less, Greater или Equal

std::cmp::Eq проблема с полным эквивалентным отношением для f32 и f64

std-traits

В Rust трейт Eq подразумевает полное эквивалентное отношение, которое должно быть рефлексивным, симметричным и транзитивным. Из-за особенностей чисел с плавающей запятой и их взаимодействия с NaN, f32 и f64 могут нарушить эти свойства. Поэтому они реализуют только трейт PartialEq, который не требует рефлексивности.

В соответствии с стандартом IEEE 754, числа NaN (Not a Number) имеют особое поведение — они не равны ни одному числу, включая себя. То есть, выражение NaN == NaN возвращает false. Это нарушает ключевую аксиому эквивалентности: рефлексивность, которая требует, чтобы для любого значения x выражение x == x было истинным.

Когда вы пытаетесь использовать #[derive(Eq)] для структуры, содержащей f32, компилятор выдаст ошибку, потому что для автогенерации трейта Eq все поля структуры должны также реализовывать Eq. Поскольку f32 не реализует Eq, компилятор не сможет автоматически сгенерировать реализацию этого трейта для вашей структуры.

Как решить проблему?

struct Oddity(f32);

// Реализуем PartialEq, чтобы правильно обрабатывать NaN
impl PartialEq for Oddity {
    fn eq(&self, other: &Self) -> bool {
        // Два числа считаются равными, если они одинаковы или оба NaN
        self.0 == other.0 || (self.0.is_nan() && other.0.is_nan())
    }
}

// Реализуем Eq, чтобы удовлетворять требованиям для типа, который реализует Eq
impl Eq for Oddity {}

// Реализуем PartialOrd, чтобы поддерживать сравнения
impl PartialOrd for Oddity {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        // Сравниваем числа, используя partial_cmp, игнорируя NaN
        self.0.partial_cmp(&other.0)
    }
}

// Реализуем Ord, чтобы поддерживать сортировку
impl Ord for Oddity {
    fn cmp(&self, other: &Self) -> Ordering {
        // Используем unwrap() здесь осторожно: не должно быть NaN при использовании Ord
        self.partial_cmp(other).unwrap()
    }
}
fn main(){
   let a = Oddity(1.0);
   let b = Oddity(2.0);
   let c = Oddity(f32::NAN);
   let d = Oddity(f32::NAN);

 // Сравнение значений
   println!("a == b: {}", a == b); // false
   println!("a < b: {}", a < b);   // true
   println!("c == d: {}", c == d); // true (оба NaN)

 // Сравнение и сортировка
   let mut vec = vec![b, a, c];
   vec.sort();  // Сортирует элементы
   println!("Отсортированный вектор: {:?}", vec);
}

Обратите внимание, что при использовании трейта Ord, код предполагает, что NaN не будет присутствовать, иначе вызов unwrap приведёт к панике. Поэтому важно быть осторожным при сортировке или других операциях, предполагающих полный порядок.

Trait std::cmp::PartialEq - отношение частичной эквивалентности

Как я могу сравнить два разных типа?

pub trait PartialEq<Rhs = Self>
where
    Rhs: ?Sized,
{
    // Required method
    fn eq(&self, other: &Rhs) -> bool;

    // Provided method
    fn ne(&self, other: &Rhs) -> bool { ... }
}

#[derive(PartialEq)]
enum BookFormat {
    Paperback,
    Hardback,
    Ebook,
}

struct Book {
    isbn: i32,
    format: BookFormat,
}

// Implement  ==  comparisons
impl PartialEq for Book {
    fn eq(&self, other: &BookFormat) -> bool {
        self.format == *other
    }
}

// Implement  ==  comparisons
impl PartialEq for BookFormat {
    fn eq(&self, other: &Book) -> bool {
        *self == other.format
    }
}
impl PartialEq for Book {
    fn eq(&self, other: &Book) -> bool {
        (*self).isbn.eq(&other.isbn) && self.format.eq(&other.format)   
    }
}
fn main(){
    let b1 = Book { isbn: 3, format: BookFormat::Paperback };

    // Сравнение типов Book vs BookFormat
    assert!(b1 == BookFormat::Paperback);
    assert!(BookFormat::Ebook != b1);
    
    // Сравнение типов Book vs Book
    let b2 = Book { isbn: 3, format: BookFormat::Paperback };
    assert!(b1 == b2);
}

Trait std::cmp::Eq - отношение полной эквивалентности

Как я могу реализовать Eq?

pub trait Eq: PartialEq<Self> { }

trait.Eq

Обратите внимание, что derive стратегия требует, чтобы все поля были Eq, что не всегда желательно.

enum BookFormat { Paperback, Hardback, Ebook }
struct Book {
    isbn: i32,
    format: BookFormat,
}
impl PartialEq for Book {
    fn eq(&self, other: &Self) -> bool {
        self.isbn == other.isbn
    }
}
impl Eq for Book {}
fn main(){}

Trait std::cmp::PartialOrd

pub trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
where
    Rhs: ?Sized,
{
    // Required method
    fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;

    // Provided methods
    fn lt(&self, other: &Rhs) -> bool { ... }
    fn le(&self, other: &Rhs) -> bool { ... }
    fn gt(&self, other: &Rhs) -> bool { ... }
    fn ge(&self, other: &Rhs) -> bool { ... }
}

Если ваш тип Ord, вы можете реализовать partial_cmp с помощью cmp:

use std::cmp::Ordering;

#[derive(Eq)]
struct Person {
    id: u32,
    name: String,
    height: u32,
}

impl PartialOrd for Person {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Person {
    fn cmp(&self, other: &Self) -> Ordering {
        self.height.cmp(&other.height)
    }
}

impl PartialEq for Person {
    fn eq(&self, other: &Self) -> bool {
        self.height == other.height
    }
}

Если ваш тип не Ord, вы реализуете partial_cmp:
struct Person {
    id: u32,
    name: String,
    height: f64,
}

impl PartialOrd for Person {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.height.partial_cmp(&other.height)
    }
}
fn main(){}

Как я могу реализовать Ord?

Trait std::cmp::Ord - Трейт для типов, образующих общий порядок

pub trait Ord: Eq + PartialOrd<Self> {
    // Required method
    fn cmp(&self, other: &Self) -> Ordering;

    // Provided methods
    fn max(self, other: Self) -> Self
       where Self: Sized { ... }
    fn min(self, other: Self) -> Self
       where Self: Sized { ... }
    fn clamp(self, min: Self, max: Self) -> Self
       where Self: Sized + PartialOrd<Self> { ... }
}

Ord требует, чтобы тип также был PartialOrd и Eq (для чего требуется PartialEq)

use std::cmp::Ordering;

#[derive(Eq)]
struct Person {
    id: u32,
    name: String,
    height: u32,
}

impl Ord for Person {
    fn cmp(&self, other: &Self) -> Ordering {
        self.height.cmp(&other.height)
    }
}

impl PartialOrd for Person {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for Person {
    fn eq(&self, other: &Self) -> bool {
        self.height == other.height
    }
}
fn main(){}

Типы реализации Hash могут быть хэшированы с помощью экземпляра Hasher.

Hash черта используется для создания одного значения, которое имеет высокую вероятность быть разным для разных элементов

При реализации как Hash, так и Eq важно, чтобы выполнялось следующее свойство:

k1 == k2 -> hash (k1) == hash (k2)

Другими словами, если два ключа равны, их хэши должны также быть равны. HashMap и HashSet полагаются на это поведение.

Элементы (согласно Eq) всегда создавали один и тот же хэш: если x == y(через Eq), то всегда должно быть верно, что hash(x) == hash(y).

Если у вас есть ручная Eq реализация, проверьте, нужна ли вам также ручная реализация Hash для соответствия этому требованию.

К счастью, вам не нужно беспокоиться об отстаивании этого свойства при получении как Eq, так и Hash с помощью [derive(PartialEq, Eq, Hash)]

impl Hash


use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;

// [derive(PartialEq, Eq, Hash)]
struct Person {
    id: u32,
    name: String,
    phone: u64,
}

impl Hash for Person {
    fn hash(&self, state: &mut H) {
        self.id.hash(state);
        self.phone.hash(state);
    }
}
fn main(){
   let mut hasher = DefaultHasher::new();
   Person{id:33,name:String::from("Jeka"),phone:380}.hash(&mut hasher);
   assert_eq!("b63f769529f448a9",format!("{:x}",hasher.finish()));
}
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
fn main(){
   let mut hasher = DefaultHasher::new();
   let data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];

   hasher.write(&data);

   println!("Hash is {:x}!", hasher.finish());
}

Структура в качестве ключа HashMap

use std::hash::{Hash, Hasher};
#[derive(Debug,Eq, PartialEq)] // #[derive(Hash)]
struct Person {
    id: u32,
    name: String,
    phone: u64,
}
impl Hash for Person {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.phone.hash(state);
    }
}
fn main(){
    let person:Person = Person{id:1u32,name:String::from(""),phone:2u64};
    let mut hasher = std::collections::hash_map::DefaultHasher::new();
    person.hash(&mut hasher);
    println!("Result: {:x}",hasher.finish() );// 7209bbd64c42b281
}

use std::collections::HashMap;
let mut hash_map = HashMap::new();
fn main(){
    hash_map.insert(
       Person{id:1u32,name:String::from(""),phone:2u64},
       "My favorite.".to_string(),
    );
}

По умолчанию в хэш-таблицах Rust используется Siphash 1-3 , хеш-функция высокого качества, но довольно медленная. Напротив, компилятор Rust использует как вызываемую хеш-функцию FxHasher, что на удивление просто, но эффективно.

Чтобы поместить тип в хеш-таблицу, необходимо вычислить для него хеш-значение. Вычисление хеш-значения типа в Rust состоит из двух частей.

Во-первых, это Hash trait. Это определяет, как хеш-функция должна проходить по типу, но не определяет саму хеш-функцию.

Во-вторых, это Hasher trait. Как только вы перейдете к скалярным типам, таким как целые числа, в игру вступит вторая часть: Hasher черта, которая определяет фактическую хеш-функцию. Он работает с байтовыми срезами и, возможно, с целыми числами. Hasher finish() метод призван производить конечное u64 значение, которое является фактическим значением хеш-функции, используемым хеш-таблицей.

crate blake2

extern crate blake2;
use std::hash::{Hash, Hasher};
#[derive(Debug,Eq, PartialEq)]
struct Person {
    id: u32,
    name: String,
    phone: u64,
}
impl Hash for Person {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.phone.hash(state);
    }
}
impl From<Person> for &'static [u8] {
    fn from(p: Person) -> Self {
       b"1"
    }
}     
fn main(){
    let person:Person = Person{id:1u32,name:String::from(""),phone:2u64};
    let mut hasher = Blake2b::new();
    let v:&'static [u8] =  From::from(person); 
    hasher.input(v);
    let hash = hasher.result();
    println!("Result: {:x}", hash);
//1ced8f5be2db23a6513eba4d819c73806424748a7bc6fa0d792cc1c7d1775a9778e894aa91413f6eb79ad5ae2f871eafcc78797e4c82af6d1cbfb1a294a10d10
}

Типаж Index IndexMut

std::ops::Index

std::ops::IndexMut

trait.Index

trait.IndexMut

Используется для операций индексирования buff[index] в неизменяемых/изменяемых контекстах.

std::ops::IndexMut

nucleotide_count[Nucleotide::A] == *nucleotide_count.index(Nucleotide::A)

use std::ops::Index;
enum Nucleotide {
    A,
    C,
    G,
    T,
}
struct NucleotideCount {
    a: usize,
    c: usize,
    g: usize,
    t: usize,
}
impl Index<Nucleotide> for NucleotideCount {
    type Output = usize;

    fn index(&self, nucleotide: Nucleotide) -> &Self::Output {
        match nucleotide {
            Nucleotide::A => &self.a,
            Nucleotide::C => &self.c,
            Nucleotide::G => &self.g,
            Nucleotide::T => &self.t,
        }
    }
}
fn main(){
   let nucleotide_count = NucleotideCount {a: 14, c: 9, g: 10, t: 12};
   assert_eq!(nucleotide_count[Nucleotide::A], 14);
   assert_eq!(nucleotide_count[Nucleotide::C], 9);
   assert_eq!(nucleotide_count[Nucleotide::G], 10);
   assert_eq!(nucleotide_count[Nucleotide::T], 12);
}

std::ops::IndexMut

use std::ops::{Index, IndexMut};
#[derive(Debug)]
enum Side {
    Left,
    Right,
}
#[derive(Debug, PartialEq)]
enum Weight {
    Kilogram(f32),
    Pound(f32),
}
struct Balance {
    pub left: Weight,
    pub right: Weight,
}
impl Index<Side> for Balance {
    type Output = Weight;

    fn index(&self, index: Side) -> &Self::Output {
        println!("Accessing {index:?}-side of balance immutably");
        match index {
            Side::Left => &self.left,
            Side::Right => &self.right,
        }
    }
}
impl IndexMut<Side> for Balance {
    fn index_mut(&mut self, index: Side) -> &mut Self::Output {
        println!("Accessing {index:?}-side of balance mutably");
        match index {
            Side::Left => &mut self.left,
            Side::Right => &mut self.right,
        }
    }
}
fn main(){
   let mut balance = Balance {
      right: Weight::Kilogram(2.5),
      left: Weight::Pound(1.5),
   };
 // В этом случае `balance[Side::Right]` — это сахар для 
 // `*balance.index(Side::Right)`, поскольку мы только читаем
 // `balance[Side::Right]`, а не записываем его.
   assert_eq!(balance[Side::Right], Weight::Kilogram(2.5));

 // Однако в данном случае `balance[Side::Left]` — это сахар для 
 // `*balance.index_mut(Side::Left)`, поскольку мы пишем `balance[Side::Left]`.
   balance[Side::Left] = Weight::Kilogram(3.0);
}