let a = String::from("test");
let b = a.deref();Тип b будет &str (если бы там был вектор, тип бы стал &[i32]). Чувствуете разницу? Этот метод сделает указатель непосредственно на данные, но он не попытается эти данные куда-то перенести. Если же перед deref поставить звёздочку, то мы опять получим ошибку:
*(a.deref()), потому что опять будет сделана попытка вытащить сами данные из heap и куда-то их перетащить. Фактически, здесь
*(a.deref()) равносильно
*a.
При этом для простых типов
deref не имеет смысла. На типах вроде i32 его нет вовсе, на указателях это метод просто вернёт сам указатель:
let a: i32 = 5;
let b = &a;
let c = b.deref();
Тип c будет &i32, как у b.
Интересно, что в ряде случаев Rust умеет делать dereference автоматически для нашего же удобства. К примеру, есть функция:
fn call(s: &str) {
println!("{}", s);
}Она может принять как &str, так и String:
let a = "test";
let b = String::from(a);
call(a);
call(&b);
Работает это потому, что у String есть trait Deref (про него будет в видео), который умеет делать dereferencing в обычный &str.
То есть функция делает что-то такое:
let borrowed = &b;
let my_str = c.deref();
Тип my_str будет &str, то есть ровно то, что написано в функции. Хотя мы могли бы сделать слайс &str сами, это уж очень неудобно, ведь нужно получить сами данные из умного указателя, сделать заимствование, и потом создать слайс:
let borrowed = &b;
let my_str = &(*borrowed)[..];
А так deref() вызовется для нас, причём столько раз, сколько требуется, поэтому если функция принимает массив вида
&[i32], то туда можно запихнуть и вектор с числами того же типа.
Теперь мы понимаем, какие есть указатели и как из них получать данные. Интересно, что мы можем создавать и свои умные указатели в Rust, и самым простым является
Box - он положит наши данные в heap и сделает привязку к ним по аналогии с тем, что делает vector или string:
let a: Box<i32> = Box::new(42);
То есть наши число теперь торчит не в стеке, а в heap, и a содержит в себе умный указатель. Это значит, что мы можем тоже делать referencing и dereferencing:
let a: Box<i32> = Box::new(42);
let b: &Box<i32> = &a;
let c: &i32 = a.deref();
let d: i32 = *a;
Из аннотации типов можно легко видеть, где что получается. Кстати,
*a тут тоже работает в отличие от истории с векторами и строками, потому что размер целого числа точно известен, его можно скопировать и поместить в стек. То есть в d будет число, независимое от того, на которое указывает a (там вообще выходит, что он сначала делает deref, потом звёздочку).
Однако вот так всё равно сделать нельзя:
let e = *b;, как и в предыдущих примерах.
*b укажет на сам
Box<i32>, копировать его автоматом Rust не будет, так что там придётся написать
clone()Но тут вопрос: зачем нам вообще этот box нужен, если лучше хранить простые типы в стеке, который банально быстрее работает? Это так и есть, во многих случаях хранить обычное число "в коробке" смысла нет. Но если у нас есть данные, размер которых заранее неизвестен, то это может быть очень актуально.
К примеру, есть вот такая структура, описывающая кошку:
struct Cat {
name: String,
age: u8,
}
let cat = Cat {
name: String::from("Mr. Buttons"),
age: 3,
};Где будет хранится наш Mr. Buttons? Сама структура будет в стеке, но строка как таковая будет в heap (в стеке же будет указатель на неё).
Однако предположим, что у кошки может быть предок, и это тоже кошка:
struct Cat {
name: String,
age: u8,
parent: Option<Cat>,
}Можно ли такую структуру запихнуть в стек? Нет, потому что она рекурсивная, и её размер во время компиляции определить невозможно. Дело в том, что у Mr. Buttons может быть потомок, у того свой потомок, и так до бесконечности. Поэтому родителя нужно хранить в heap, и это тот случай, когда нам нужен Box:
struct Cat {
name: String,
age: u8,
parent: Option<Box<Cat>>,
}Создадим двух кошек: