27 января 2020 года. 21 минута чтения
Содержание
- Привязки переменных
- Ключевое слово
let
- Аннотация типа
- Неинициализированные переменные
- Отбрасывание значений
- Затенение привязок
- Ключевое слово
- Кортежи
- Деструктуризация кортежей
- Операторы и выражения
- Блоки
- Неявный возврат
- Доступ к полям и вызов методов
- Модули и синтаксис
use
- Пространства имен типов
- Прелюдия
libstd
- Структуры
- Синтаксис обновления структур
- Деструктуризация структур
- Шаблоны и деструктуризация
- Деструктуризация с
if let
match
как шаблон- Исчерпывающие
match
- Деструктуризация с
- Методы
- Неизменяемость
- Трейты
- Правила сиротства
- Тип
Self
- Маркерные трейты
- Получатели методов трейтов
- Автоматическое создание трейтов
- Дженерики
- Дженерик-функции
- Ограничения параметров типа (границы трейтов)
- Мономорфизация
- Дженерик-структуры
- Пример:
Vec
- Макросы
- Макрос
panic!
- Функции, вызывающие панику
- Макрос
- Перечисления (суммарные типы)
- Время жизни
- Правила заимствования (одно или несколько неизменяемых заимствований XOR одно изменяемое заимствование)
- Функции с обобщенными параметрами времени жизни
- Опущение времени жизни
- Структуры с обобщенным временем жизни
- Реализации с обобщенным временем жизни
- Время жизни
'static
- Собственные и ссылочные типы
- Срезы
- Перегрузка операторов
- Правила заимствования и срезы
- Срезы строк (
&str
) - Ошибочные функции (
Result
)
- Разыменование
- Типы функций, замыкания
FnMut
и правила заимствования- Замыкание туалетного типа
- Циклы, итераторы
- Возвращение замыканий
- Захват в замыкания
let
вводит привязку переменной:
let x; // объявление "x"
x = 42; // присвоение 42 "x"
Это также можно записать в одну строку:
let x = 42;
Вы можете явно указать тип переменной с помощью :
, это называется аннотацией типа:
let x: i32; // `i32` - это знаковое 32-битное целое число
x = 42;
// существуют также i8, i16, i32, i64, i128
// и также u8, u16, u32, u64, u128 для беззнаковых
Это также можно записать в одну строку:
let x: i32 = 42;
Если вы объявляете имя и инициализируете его позже, компилятор не позволит вам использовать его до инициализации.
let x;
foobar(x); // ошибка: заимствование возможно неинициализированной переменной: `x`
x = 42;
Однако, так делать можно:
let x;
x = 42;
foobar(x); // тип `x` будет выведен отсюда
Подчеркивание _
- это особое имя - точнее, "отсутствие имени". Оно означает, что что-то нужно выбросить:
// это делает *ничего*, потому что 42 - это константа
let _ = 42;
// это вызывает `get_thing`, но выбрасывает его результат
let _ = get_thing();
Имена, начинающиеся с подчеркивания, являются обычными именами, но компилятор не будет предупреждать о их неиспользовании:
// возможно, мы будем использовать `_x` позже, но наш код еще не завершен
// и мы просто хотели избавиться от предупреждения компилятора на данный момент.
let _x = 42;
Можно вводить отдельные привязки с одинаковым именем - можно затенять привязку переменной:
let x = 13;
let x = x + 3;
// использование `x` после этой строки относится только ко второму `x`,
// хотя первый `x` все еще существует (он будет удален при выходе из области видимости),
// вы больше не можете к нему обращаться.
Rust имеет кортежи, которые можно рассматривать как "фиксированной длины коллекции значений разных типов".
let pair = ('a', 17);
pair.0; // это 'a'
pair.1; // это 17
Если мы действительно хотим аннотировать тип пары, мы бы написали:
let pair: (char, i32) = ('a', 17);
Кортежи можно деструктурировать при присвоении, что означает их разбиение на отдельные поля:
let (some_char, some_int) = ('a', 17);
// теперь `some_char` это 'a', а `some_int` это 17
Это особенно полезно, когда функция возвращает кортеж:
let (left, right) = slice.split_at(middle);
Конечно, при деструктуризации кортежа можно использовать _
, чтобы отбросить его часть:
let (_, right) = slice.split_at(middle);
Блоки обрамляются фигурными скобками и имеют свою собственную область видимости:
// Это печатает "in", затем "out"
fn main() {
let x = "out";
{
// это другой `x`
let x = "in";
println!("{}", x);
}
println!("{}", x);
}
Блоки также являются выражениями, что означает, что они вычисляются в значение.
// это:
let x = 42;
// эквивалентно этому:
let x = { 42 };
Внутри блока могут быть несколько операторов:
let x = {
let y = 1; // первый оператор
let z = 2; // второй оператор
y + z // это *хвост* - то, во что будет вычисляться весь блок
};
Из-за этого "опускание точки с запятой в конце функции" эквивалентно возврату значения, то есть эти функции эквивалентны:
fn fair_dice_roll() -> i32 {
return 4;
}
fn fair_dice_roll() -> i32 {
4
}
if
условные выражения также являются выражениями:
fn fair_dice_roll() -> i32 {
if feeling_lucky {
6
} else {
4
}
}
match
также является выражением:
fn fair_dice_roll() -> i32 {
match feeling_lucky {
true => 6,
false => 4,
}
}
Точки обычно используются для доступа к полям значения:
let a = (10, 20);
a.0; // это 10
let amos = get_some_struct();
amos.nickname; // это "fasterthanlime"
Или для вызова метода на значении:
let nick = "fasterthanlime";
nick.len(); // это 14
Двойное двоеточие ::
аналогично, но оно работает с пространствами имен.
В этом примере, std
- это crate (~ библиотека), cmp
- это модуль (~ исходный файл), а min
- это функция:
let least = std::cmp::min(3, 8); // это 3
Директивы use
могут использоваться для "приведения в область видимости" имен из других пространств имен:
use std::cmp::min;
let least = min(7, 1); // это 1
В пределах дир
ектив use
фигурные скобки имеют другое значение: они являются "глобами". Если мы хотим импортировать и min
, и max
, мы можем сделать любое из этих:
// это работает:
use std::cmp::min;
use std::cmp::max;
// это тоже работает:
use std::cmp::{min, max};
// это тоже работает!
use std::{cmp::min, cmp::max};
Подстановочный знак (*
) позволяет импортировать каждый символ из пространства имен:
// это привносит `min` и `max` в область видимости, и многие другие вещи
use std::cmp::*;
Типы также являются пространствами имен, и методы могут быть вызваны как обычные функции:
let x = "amos".len(); // это 4
let x = str::len("amos"); // это тоже 4
str
- это примитивный тип, но многие непримитивные типы также находятся в области видимости по умолчанию.
// `Vec` - это обычная структура, а не примитивный тип
let v = Vec::new();
// это точно такой же код, но с *полным* путем к `Vec`
let v = std::vec::Vec::new();
Это работает, потому что Rust вставляет это в начале каждого модуля:
use std::prelude::v1::*;
(Что, в свою очередь, повторно экспортирует множество символов, таких как Vec
, String
, Option
и Result
).
Структуры объявляются с помощью ключевого слова struct
:
struct Vec2 {
x: f64, // 64-битное число с плавающей точкой, также известное как "двойная точность"
y: f64,
}
Их можно инициализировать, используя литералы структур:
let v1 = Vec2 { x: 1.0, y: 3.0 };
let v2 = Vec2 { y: 2.0, x: 4.0 };
// порядок не имеет значения, только имена
Существует сокращение для инициализации остальных полей из другой структуры:
let v3 = Vec2 {
x: 14.0,
..v2
};
Это называется "синтаксис обновления структур", может быть только в последней позиции и не может следовать за запятой.
Обратите внимание, что остальные поля могут означать все поля:
let v4 = Vec2 { ..v3 };
Структуры, как и кортежи, могут быть деструктурированы.
Так же, как это является допустимым шаблоном let
:
let (left, right) = slice.split_at(middle);
Так и это:
let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` теперь 3.0, `y` теперь 6.0
И это:
let Vec2 { x, .. } = v;
// это выбрасывает `v.y`
Шаблоны let
могут использоваться как условия в if
:
struct Number {
odd: bool,
value: i32,
}
fn main() {
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
print_number(one);
print_number(two);
}
fn print_number(n: Number) {
if let Number { odd: true, value } = n {
println!("Odd number: {}", value);
} else if let Number { odd: false, value } = n {
println!("Even number: {}", value);
}
}
// это печатает:
// Odd number: 1
// Even number: 2
Армы match
также являются шаблонами, так же как if let
:
fn print_number(n: Number) {
match n {
Number { odd: true, value } => println!("Odd number: {}", value),
Number { odd: false, value } => println!("Even number: {}", value),
}
}
// это печатает то же самое, что и раньше
match
должен быть исчерпывающим: хотя бы один арм должен соответствовать.
fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// если бы последний арм не существовал, мы бы получили ошибку во время компиляции
}
}
Если это сложно, можно использовать _
как "шаблон на все случаи":
fn print_number(n: Number) {
match n.value {
1 => println!("One"),
2 => println!("Two"),
_ => println!("{}", n.value),
}
}
Вы можете объявлять методы для своих типов:
struct Number {
odd: bool,
value: i32,
}
impl Number {
fn is_strictly_positive(self) -> bool {
self.value > 0
}
}
И использовать их, как обычно:
fn main() {
let minus_two = Number {
odd: false,
value: -2,
};
println!("positive? {}", minus_two.is_strictly_positive());
// это печатает "positive? false"
}
Привязки переменных по умолчанию неизменяемы, что означает, что их внутреннее состояние не может быть изменено:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n.odd = false; // ошибка: нельзя присваивать `n.odd`, так как `n` не объявлено изменяемым
}
И также они не могут быть присвоены повторно:
fn main() {
let n = Number {
odd: true,
value: 17,
};
n = Number {
odd: false,
value: 22,
}; // ошибка: нельзя присваивать дважды неизменяемой переменной `n`
}
mut
делает привязку переменной изменяемой:
fn main() {
let mut n = Number {
odd: true,
value: 17,
};
n.value = 19; // все нормально
}
Трейты - это то, что могут иметь несколько типов:
trait Signed {
fn is_strictly_negative(self) -> bool;
}
Вы можете реализовать:
- один из ваших трейтов для чужого типа
- чужой трейт для одного из ваших типов
но не чужой трейт для чужого типа.
Эти правила называются "правилами сиротства".
Вот реализация нашего трейта для нашего типа:
impl Signed for Number {
fn is_strictly_negative(self) -> bool {
self.value < 0
}
}
fn main() {
let n = Number { odd: false, value: -44 };
println!("{}", n.is_strictly_negative()); // печатает "true"
}
Наш трейт для чужого типа (даже примитивного):
impl Signed for i32 {
fn is_strictly_negative(self) -> bool {
self < 0
}
}
fn main() {
let n: i32 = -44;
println!("{}", n.is_strictly_negative()); // печатает "true"
}
Чужой трейт для нашего типа:
// трейт `Neg` используется для перегрузки унарного оператора минус.
impl std::ops::Neg for Number {
type Output = Number;
fn neg(self) -> Number {
Number {
value: -self.value,
odd: self.odd,
}
}
}
fn main() {
let n = Number { odd: true, value: 987 };
let m = -n; // это возможно только потому, что мы реализовали `Neg`
println!("{}", m.value); // печатает "-987"
}
Блок impl
всегда для типа, поэтому внутри этого блока Self
означает этот тип:
impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self.value,
odd: self.odd,
}
}
}
Некоторые трейты являются маркерными - они не указывают, что тип реализует какие-то методы, они указывают, что с типом можно делать определенные вещи.
Нап
ример, i32
реализует трейт Copy
(коротко, i32
является Copy
), так что это работает:
fn main() {
let a: i32 = 15;
let b = a; // `a` копируется
let c = a; // `a` снова копируется
}
И это тоже работает:
fn print_i32(x: i32) {
println!("x = {}", x);
}
fn main() {
let a: i32 = 15;
print_i32(a); // `a` копируется
print_i32(a); // `a` снова копируется
}
Но структура Number
не является Copy
, поэтому это не работает:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `n` перемещается в `m`
let o = n; // ошибка: использование перемещенного значения: `n`
}
И это тоже не работает:
fn print_number(n: Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(n); // `n` перемещается
print_number(n); // ошибка: использование перемещенного значения: `n`
}
Но это работает, если print_number
принимает неизменяемую ссылку:
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(&n); // `n` заимствовано на время вызова
print_number(&n); // `n` заимствовано снова
}
Это также работает, если функция принимает изменяемую ссылку - но только если привязка переменной также mut
.
fn invert(n: &mut Number) {
n.value = -n.value;
}
fn print_number(n: &Number) {
println!("{} number {}", if n.odd { "odd" } else { "even" }, n.value);
}
fn main() {
// на этот раз `n` изменяемая
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n); // `n` заимствовано изменяемо - все явно
print_number(&n);
}
Методы трейтов также могут принимать self
по ссылке или изменяемой ссылке:
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
При вызове методов трейтов получатель заимствуется неявно:
fn main() {
let n = Number { odd: true, value: 51 };
let mut m = n.clone();
m.value += 100;
print_number(&n);
print_number(&m);
}
Чтобы подчеркнуть это: эти выражения эквивалентны:
let m = n.clone();
let m = std::clone::Clone::clone(&n);
Маркерные трейты, такие как Copy
, не имеют методов:
// заметьте: `Copy` требует, чтобы был реализован также `Clone`
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
impl std::marker::Copy for Number {}
Теперь Clone
все еще можно использовать:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n.clone();
let o = n.clone();
}
Но значения Number
больше не будут перемещаться:
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // `m` является копией `n`
let o = n; // тоже самое. `n` не перемещается и не заимствуется.
}
Некоторые трейты настолько распространены, что они могут быть автоматически реализованы с использованием атрибута derive
:
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
// это разворачивается в `impl Clone for Number` и `impl Copy for Number`.
Функции могут быть обобщенными:
fn foobar<T>(arg: T) {
// делаем что-то с `arg`
}
Они могут иметь несколько параметров типа, которые затем могут использоваться в объявлении функции и в ее теле, вместо конкретных типов:
fn foobar<L, R>(left: L, right: R) {
// делаем что-то с `left` и `right`
}
Параметры типа обычно имеют ограничения, чтобы вы могли что-то с ними делать.
Простые ограничения - это просто имена трейтов:
fn print<T: Display>(value: T) {
println!("value = {}", value);
}
fn print<T: Debug>(value: T) {
println!("value = {:?}", value);
}
Существует более длинный синтаксис для ограничений параметров типа:
fn print<T>(value: T)
where
T: Display,
{
println!("value = {}", value);
}
Ограничения могут быть более сложными: они могут требовать, чтобы параметр типа реализовывал несколько трейтов:
use std::fmt::Debug;
fn compare<T>(left: T, right: T)
where
T: Debug + PartialEq,
{
println!("{:?} {} {:?}", left, if left == right { "==" } else { "!=" }, right);
}
fn main() {
compare("tea", "coffee");
// печатает: "tea" != "coffee"
}
Обобщенные функции можно рассматривать как пространства имен, содержащие бесконечное количество функций с разными конкретными типами.
Так же, как с crate, модулями и типами, обобщенные функции можно "исследовать" (навигация) с помощью ::
.
fn main() {
use std::any::type_name;
println!("{}", type_name::<i32>()); // печатает "i32"
println!("{}", type_name::<(f64, char)>()); // печатает "(f64, char)"
}
Это любовно называется синтаксисом turbofish, потому что ::<>
выглядит как рыбка.
Структуры также могут быть обобщенными:
struct Pair<T> {
a: T,
b: T,
}
fn print_type_name<T>(_val: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // печатает "Pair<i32>"
print_type_name(&p2); // печатает "Pair<bool>"
}
Тип стандартной библиотеки Vec
(~ массив в куче), является обобщенным:
fn main() {
let mut v1 = Vec::new();
v1.push(1);
let mut v2 = Vec::new();
v2.push(false);
print_type_name(&v1); // печатает "Vec<i32>"
print_type_name(&v2); // печатает "Vec<bool>"
}
Говоря о Vec
, он имеет макрос, который более или менее предоставляет "литералы вектора":
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // печатает "Vec<i32>"
print_type_name(&v2); // печатает "Vec<bool>"
}
Все name!()
, name![]
или name!{}
вызывают макрос. Макросы просто разворачиваются в обычный код.
Фактически, println
- это макрос:
fn main() {
println!("{}", "Hello there!");
}
Это разворачивается в нечто, что имеет такой же эффект, как:
fn main() {
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}
panic
также является макросом. Он жестоко останавливает выполнение с сообщением об ошибке и именем файла / номером строки ошибки, если включен:
fn main() {
panic!("This panics");
}
// вывод: thread 'main' panicked at '
This panics', src/main.rs:3:5
Некоторые методы также вызывают панику. Например, тип Option
может содержать что-то или ничего. Если вызвать .unwrap()
на нем, и он содержит ничего, это вызывает панику:
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // это нормально
let o2: Option<i32> = None;
o2.unwrap(); // это вызывает панику!
}
// вывод: thread 'main' panicked at 'called `Option::unwrap()` on a `None` value
Option
- это не структура, а перечисление с двумя вариантами.
enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
// варианты перечислений можно использовать в шаблонах:
match self {
Self::Some(t) => t,
Self::None => panic!(".unwrap() called on a None option"),
}
}
}
use self::Option::{None, Some};
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // это нормально
let o2: Option<i32> = None;
o2.unwrap(); // это вызывает панику!
// вывод: thread 'main' panicked at '.unwrap() called on a None option', src/mai
Result
также является перечислением, которое может содержать что-то или ошибку:
enum Result<T, E> {
Ok(T),
Err(E),
}
Он также вызывает панику, когда разворачивается и содержит ошибку.
Привязки переменных имеют "время жизни":
fn main() {
// `x` еще не существует
{
let x = 42; // `x` начинает существовать
println!("x = {}", x);
// `x` перестает существовать
}
// `x` больше не существует
}
Аналогично, ссылки имеют время жизни:
fn main() {
// `x` еще не существует
{
let x = 42; // `x` начинает существовать
let x_ref = &x; // `x_ref` начинает существовать - оно заимствует `x`
println!("x_ref = {}", x_ref);
// `x_ref` перестает существовать
// `x` перестает существовать
}
// `x` больше не существует
}
Время жизни ссылки не может превышать время жизни привязки переменной, которую она заимствует:
fn main() {
let x_ref = {
let x = 42;
&x
};
println!("x_ref = {}", x_ref);
// ошибка: `x` живет недостаточно долго
}
Правила заимствования (одно или несколько неизменяемых заимствований XOR одно изменяемое заимствование)
Привязка переменной может быть неизменяемо заимствована несколько раз:
fn main() {
let x = 42;
let x_ref1 = &x;
let x_ref2 = &x;
let x_ref3 = &x;
println!("{} {} {}", x_ref1, x_ref2, x_ref3);
}
Во время заимствования переменную нельзя изменять:
fn main() {
let mut x = 42;
let x_ref = &x;
x = 13;
println!("x_ref = {}", x_ref);
// ошибка: нельзя присваивать `x`, потому что она заимствована
}
Во время неизменяемого заимствования переменную нельзя заимствовать изменяемо:
fn main() {
let mut x = 42;
let x_ref1 = &x;
let x_ref2 = &mut x;
// ошибка: нельзя заимствовать `x` как изменяемую, потому что она уже заимствована как неизменяемая
println!("x_ref1 = {}", x_ref1);
}
Ссылки в аргументах функции также имеют время жизни:
fn print(x: &i32) {
// `x` заимствовано (снаружи) на все время вызова этой функции.
}
Функции с аргументами-ссылками можно вызывать с заимствованиями, которые имеют разное время жизни, поэтому:
Все функции, принимающие ссылки, являются обобщенными.
Время жизни - это параметры обобщений.
Имена времени жизни начинаются с одиночной кавычки '
:
// опущенное (неназванное) время жизни:
fn print(x: &i32) {}
// названное время жизни:
fn print<'a>(x: &'a i32) {}
Это позволяет возвращать ссылки, время жизни которых зависит от времени жизни аргументов:
struct Number {
value: i32,
}
fn number_value<'a>(num: &'a Number) -> &'a i32 {
&num.value
}
fn main() {
let n = Number { value: 47 };
let v = number_value(&n);
// `v` заимствует `n` (неизменяемо), таким образом: `v` не может пережить `n`.
// Пока существует `v`, `n` не может быть заимствован изменяемо, изменен, перемещен и т.д.
}
Когда существует одно входное время жизни, его не нужно называть, и все имеет одно и то же время жизни, поэтому две функции ниже эквивалентны:
fn number_value<'a>(num: &'a Number) -> &'a i32 {
&num.value
}
fn number_value(num: &Number) -> &i32 {
&num.value
}
Структуры также могут быть обобщенными по времени жизни, что позволяет им содержать ссылки:
struct NumRef<'a> {
x: &'a i32,
}
fn main() {
let x: i32 = 99;
let x_ref = NumRef { x: &x };
// `x_ref` не может пережить `x` и т.д.
}
Тот же код, но с дополнительной функцией:
struct NumRef<'a> {
x: &'a i32,
}
fn as_num_ref<'a>(x: &'a i32) -> NumRef<'a> {
NumRef { x: &x }
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` не может пережить `x` и т.д.
}
Тот же код, но с "опущенным" временем жизни:
struct NumRef<'a> {
x: &'a i32,
}
fn as_num_ref(x: &i32) -> NumRef<'_> {
NumRef { x: &x }
}
fn main() {
let x: i32 = 99;
let x_ref = as_num_ref(&x);
// `x_ref` не может пережить `x` и т.д.
}
Блоки impl
могут быть обобщенными по времени жизни:
impl<'a> NumRef<'a> {
fn as_i32_ref(&'a self) -> &'a i32 {
self.x
}
}
fn main() {
let x: i32 = 99;
let x_num_ref = NumRef { x: &x };
let x_i32_ref = x_num_ref.as_i32_ref();
// ни одна из ссылок не может пережить `x`
}
Но вы также можете использовать опущение:
impl<'a> NumRef<'a> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
И можно опустить еще сильнее, если вам никогда не нужно имя:
impl NumRef<'_> {
fn as_i32_ref(&self) -> &i32 {
self.x
}
}
Существует особое время жизни, названное 'static
, которое действительно на протяжении всей жизни программы.
Строковые литералы являются 'static
:
struct Person {
name: &'static str,
}
fn main() {
let p = Person {
name: "fasterthanlime",
};
}
Но ссылки на String
не являются 'static
:
struct Person {
name: &'static str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// ошибка: `name` живет недостаточно долго
}
В последнем примере локальная переменная name
не является &'static str
, это String
. Она была выделена динамически и будет освобождена. Ее время жизни меньше, чем время жизни всей программы (даже если она находится в main
).
Чтобы хранить нестатическую строку в Person
, она должна либо:
A) быть обобщенной по времени жизни:
struct Person<'a> {
name: &'a str,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: &name };
// `p` не может пережить `name`
}
или
B) взять на себя владение строкой:
struct Person {
name: String,
}
fn main() {
let name = format!("fasterthan{}", "lime");
let p = Person { name: name };
// `name` был перемещен в `p`, их время жизни больше не связано.
}
Говоря об этом: в литерале структуры, когда поле устанавливается на переменную с тем же именем:
let p = Person { name: name };
Можно сократить это так:
let p = Person { name };
Инструменты, такие как clippy
, предложат внести эти изменения и даже применят исправление программно, если вы это разрешите.
Для многих типов в Rust существуют собственные и несобственные варианты:
- Строки:
String
является собственным,&str
является ссылкой. - Пути:
PathBuf
является собственным,&Path
является ссылкой. - Коллекции:
Vec<T>
является собственным,&[T]
является ссылкой.
Rust имеет срезы - это ссылки на несколько смежных элементов. Вы можете заимствовать срез вектора, например:
fn main() {
let v = vec![1, 2, 3, 4, 5];
let v2 = &v[2..4];
println!("v2 = {:?}", v2);
}
// вывод:
// v2 = [3, 4]
Вышеописанное не является магией. Оператор индексирования (foo[index]
) перегружен с использованием трейтов Index
и IndexMut
.
Синтаксис ..
- это просто литералы диапазона. Диапазоны - это просто несколько структур, определенных в стандартной библиотеке.
Они могут быть открытыми и их правый предел может быть включен, если перед ним стоит =
.
fn main() {
// 0 или больше
println!("{:?}", (0..).contains(&100)); // true
// строго меньше 20
println!("{:?}", (..20).contains(&20)); // false
// 20 или меньше
println!("{:?}", (..=20).contains(&20)); // true
// только 3, 4, 5
println!("{:?}", (3..6).contains(&4)); // true
}
Правила заимствования применимы к срезам.
fn tail(s: &[u8]) -> &[u8] {
&s[1..]
}
fn main() {
let x = &[1, 2, 3, 4, 5];
let y = tail(x);
println!("y = {:?}", y);
}
Это то же самое, что и:
fn tail<'a>(s: &'a [u8]) -> &'a [u8] {
&s[1..]
}
Это легально:
fn main() {
let y = {
let x = &[1, 2, 3, 4, 5];
tail(x)
};
println!("y = {:?}", y);
}
// но только потому, что [1, 2, 3, 4, 5] является 'static массивом.
Так что это незаконно:
fn main() {
let y = {
let v = vec![1, 2, 3, 4, 5];
tail(&v)
// ошибка: `v` живет недостаточно долго
};
println!("y = {:?}", y);
}
// потому что вектор выделяется в куче и имеет не 'static время жизни.
Значения &str
на самом деле являются срезами.
fn file_ext(name: &str) -> Option<&str> {
// это не создает новую строку - возвращает
// срез аргумента.
name.split(".").last()
}
fn main() {
let name = "Read me. Or don't.txt";
if let Some(ext) = file_ext(name) {
println!("file extension: {}",
ext);
} else {
println!("no file extension");
}
}
...так что правила заимствования применимы и здесь:
fn main() {
let ext = {
let name = String::from("Read me. Or don't.txt");
file_ext(&name).unwrap_or("")
// ошибка: `name` живет недостаточно долго
};
println!("extension: {:?}", ext);
}
Функции, которые могут завершиться неудачей, обычно возвращают Result
:
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]);
println!("{:?}", s);
// печатает: Ok("🍉")
let s = std::str::from_utf8(&[195, 40]);
println!("{:?}", s);
// печатает: Err(Utf8Error { valid_up_to: 0, error_len: Some(1) })
}
Если вы хотите вызвать панику в случае неудачи, вы можете использовать .unwrap()
:
fn main() {
let s = std::str::from_utf8(&[240, 159, 141, 137]).unwrap();
println!("{:?}", s);
// печатает: "🍉"
let s = std::str::from_utf8(&[195, 40]).unwrap();
// вывод: thread 'main' panicked at 'called `Result::unwrap()`
// on an `Err` value: Utf8Error { valid_up_to: 0, error_len: Some(1) }',
// src/libcore/result.rs:1165:5
}
Или .expect()
для пользовательского сообщения:
fn main() {
let s = std::str::from_utf8(&[195, 40]).expect("valid utf-8");
// вывод: thread 'main' panicked at 'valid utf-8: Utf8Error
// { valid_up_to: 0, error_len: Some(1) }', src/libcore/result.rs:1165:5
}
Или, вы можете использовать match
:
fn main() {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => panic!(e),
}
// печатает 🍉
}
Или можно использовать if let
:
fn main() {
if let Ok(s) = std::str::from_utf8(&[240, 159, 141, 137]) {
println!("{}", s);
}
// печатает 🍉
}
Или можно передать ошибку вверх по цепочке:
fn main() -> Result<(), std::str::Utf8Error> {
match std::str::from_utf8(&[240, 159, 141, 137]) {
Ok(s) => println!("{}", s),
Err(e) => return Err(e),
}
Ok(())
}
Или можно использовать ?
для краткости:
fn main() -> Result<(), std::str::Utf8Error> {
let s = std::str::from_utf8(&[240, 159, 141, 137])?;
println!("{}", s);
Ok(())
}
Оператор *
может использоваться для разыменования, но вам не нужно это делать для доступа к полям или вызова методов:
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
println!("({}, {})", p_ref.x, p_ref.y);
// печатает `(1, 3)`
}
И вы можете делать это, только если тип Copy
:
struct Point {
x: f64,
y: f64,
}
fn negate(p: Point) -> Point {
Point {
x: -p.x,
y: -p.y,
}
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref);
// ошибка: нельзя перемещать из `*p_ref`, который находится за общей ссылкой
}
Теперь Point
является Copy
:
#[derive(Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
fn negate(p: Point) -> Point {
Point {
x: -p.x,
y: -p.y,
}
}
fn main() {
let p = Point { x: 1.0, y: 3.0 };
let p_ref = &p;
negate(*p_ref); // ...и теперь это работает
}
Замыкания - это просто функции типа Fn
, FnMut
или FnOnce
с захваченным контекстом.
Их параметры - это список имен через запятую внутри пары вертикальных черт (|). Им не нужны фигурные скобки, если вы не хотите иметь несколько операторов.
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
for_each_planet(|planet| println!("Hello, {}", planet));
}
// печатает:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter
Правила заимствования применимы и к ним:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// наше замыкание заимствует `greeting`, поэтому оно не может пережить его
}
Например, это не сработает:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str) + 'static // `F` теперь должно иметь время жизни `'static`
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// ошибка: замыкание может пережить текущую функцию, но оно заимствует
// `greeting`, которое принадлежит текущей функции
}
Но это сработает:
fn main() {
let greeting = String::from("You're doing great");
for_each_planet(move |planet| println!("{}, {}", greeting, planet));
// `greeting` больше не заимствовано, оно *перемещено* в
// замыкание.
}
FnMut
нужно изменяемо заимствовать, чтобы его вызвать, так что его можно вызывать только один раз за раз.
Это законно:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
foobar(|x| x * 2);
}
// вывод: 8
А это нет:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
println!("{}", f(f(2)));
// ошибка: нельзя заимствовать `f` как изменяемую более одного раза за раз
}
fn main() {
foobar(|x| x * 2);
}
Это снова законно:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
foobar(|x| x * 2);
}
// вывод: 8
FnMut
существует, потому что некоторые замыкания изменяемо заимствуют локальные переменные:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
x * acc
});
}
// вывод: 24
Эти замыкания нельзя передать функциям, ожидающим Fn
:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
// ошибка: нельзя присваивать `acc`, так как это
// захваченная переменная в замыкании `Fn`.
// компилятор предлагает "изменить foobar
// чтобы он принимал замыкания, которые реализуют `FnMut`"
x * acc
});
}
FnOnce
замыкания можно вызвать только один раз. Они существуют, потому что некоторые замыкания перемещают переменные, которые были перемещены при захвате:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
}
fn main() {
let s = String::from("alright");
foobar(move || s);
// `s` был перемещен в наше замыкание, и наше
// замыкание перемещает его к вызывающему, возвращая
// его. Помните, что `String` не является `Copy`.
}
Это естественно соблюдается, так как FnOnce
замыкания нужно переместить, чтобы их вызвать.
Так что, например, это незаконно:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
println!("{}", f());
// ошибка: использование перемещенного значения: `f`
}
И если вам нужно доказательство, что наше замыкание действительно перемещает s
, это также незаконно:
fn main() {
let s = String::from("alright");
foobar(move || s);
foobar(move || s);
// использование перемещенного значения: `s`
}
Но это законно:
fn main() {
let s = String::from("alright");
foobar(|| s.clone());
foobar(|| s.clone());
}
Вот замыкание с двумя аргументами:
fn foobar<F>(x: i32, y: i32, is_greater: F)
where F: Fn(i32, i32) -> bool
{
let (greater, smaller) = if is_greater(x, y) {
(x, y)
} else {
(y, x)
};
println!("{} is greater than {}", greater, smaller);
}
fn main() {
foobar(32, 64, |x, y| x > y);
}
Вот замыкание, игнорирующее оба своих аргумента:
fn main() {
foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}
Вот немного тревожное замыкание:
fn countdown<F>(count: usize, tick: F)
where F: Fn(usize)
{
for i in (1..=count).rev() {
tick(i);
}
}
fn main() {
countdown(3, |i| println!("tick {}...", i));
}
// вывод:
// tick 3...
// tick 2...
// tick 1...
И вот замыкание туалетного типа:
fn main() {
countdown(3, |_| ());
}
Оно так называется, потому что |_| ()
выглядит как унитаз.
Все, что можно итерировать, можно использовать в цикле for in
.
Мы только что видели использование диапазона, но это также работает с Vec
:
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
И срез:
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
// вывод:
// I like the number 52
// I like the number 49
// I like the number 21
И даже настоящий итератор:
fn main() {
// примечание: `&str` также имеет итератор `.bytes()`.
// Тип `char` в Rust - это "скалярное значение Unicode"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
// вывод:
// Give me a r
// Give me a u
// Give me a s
// Give me a t
Даже если элементы итератора отфильтрованы и преобразованы:
fn main() {
for c in "SuRPRISE INbOUND"
.chars()
.filter(|c| c.is_lowercase())
.flat_map(|c| c.to_uppercase())
{
print!("{}", c);
}
println!();
}
// вывод: UB
Вы можете вернуть замыкание из функции:
fn make_tester(answer: String) -> impl Fn(&str) -> bool {
move |challenge| {
challenge == answer
}
}
fn main() {
// можно использовать `.into()` для выполнения преобразований
// между различными типами, здесь `&'static str` и `String`
let test = make_tester("hunter2".into());
println!("{}", test("******"));
println!("{}", test("hunter2"));
}
Вы даже можете переместить ссылку на некоторые аргументы функции в замыкание, которое она возвращает:
fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
move |challenge| {
challenge == answer
}
}
fn main() {
let test = make_tester("hunter2");
println!("{}", test("*******"));
println!("{}", test("hunter2"));
}
// вывод:
// false
// true
Или с опущенными временами жизни:
fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
move |challenge| {
challenge == answer
}
}
И на этом мы достигли отметки времени чтения в 30 минут, и вы должны быть в состоянии читать большинство кода Rust, который найдете в Интернете.
Писать на Rust - это совершенно другой опыт по сравнению с чтением Rust. С одной стороны, вы не читаете решение проблемы, вы действительно ее решаете. С другой стороны, компилятор Rust очень помогает.
Компилятор Rust имеет высококачественные диагностические сообщения (включая предложения) для всех ошибок, рассмотренных в этой статье.
И когда подсказки отсутствуют, команда компилятора не боится их добавлять.
Для получения дополнительного