[Rust] Конспектируем Книгу Rust:: Владение
Автор
Сообщение
news_bot ®
Стаж: 7 лет 2 месяца
Сообщений: 27286
Nurked предложил оригинальный способ прочтения "the book" — читать главы надо не по порядку, а в последовательности [4, 3, 5, 6, 8, 4, 9, 7, 10, 4, 13, 17, 15, 16].
Можно пойти дальше. Мне представляется, что читать будет гораздо легче и быстрее, если выкинуть бо́льшую часть текста и заменить некоторые примеры. Ниже представлен "пробник" в виде сильно сокращенной главы 4. Чтение его подразумевает наличие опыта разработки на других языках — объяснения типа "чем отличается стек от кучи", естественно, попали под оптимизацию.
Лирика
SPL
Из тех языков, с которыми я плотно работал, Rust ближе всего, КМК, к Go. Их роднит отсутствие "нормального ООП", отсутствие "нормальных исключений", концепция срезов (slice), наличие как объектов, так и ссылок/указателей на них, возможность возвращать несколько значений из функций, ну и, конечно, кросс-компиляция "из коробки". В Go пока нет обобщенных типов, но про них знают и их ждут. Поэтому опытного гофера ржавчиной не испугать.
Управление памятью
Когда владелец (owner) покидает область видимости (variable scope), его "финализируют" через вызов drop():
{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
}
// This scope is now over, and s is no longer valid
// Rust calls s.drop() automatically at the closing curly bracket.
The book утверждает, что управление памятью осуществляется через "владение" с набором правил, которые компилятор проверяет во время компиляции программы. Полезно сразу иметь в виду (напомню — материал для опытных камикадзе), что есть некие:
- Box<T> для распределения значений в куче (памяти)
- Rc<T> тип счётчика ссылок, который допускает множественное владение
- Типы Ref<T> и RefMut<T>, доступ к которым осуществляется через тип RefCell<T>, который обеспечивает правила заимствования во время выполнения, вместо времени компиляции
- Rust допускает утечки памяти, используя типы Rc<T> и RefCell<T> можно создавать связи, где элементы ссылаются друг на друга в цикле
Короче, если память выделяется в куче, а полученные объекты ссылаются друг на друга — будь готов к граблям, засадам и к поиску утечек памяти.
Воспоминание о будущем
SPL
В силу исключительной важности вопроса забегу вперед.
Интересно, что сначала the book утверждает, мол, последствия зацикливания ссылок не очень страшны, а следующим предложением идет такой текст:
However, if a more complex program allocated lots of memory in a cycle and held onto it for a long time, the program would use more memory than it needed and might overwhelm the system, causing it to run out of available memory.
Таким образом, при разработке систем с высокой нагрузкой надо быть начеку — может "натечь" много и быстро. Rust предоставляет определенный инструментарий для борьбы с этим, типа замены умного указателя Rc на Weak, но все это, конечно же, заставляет разработчика выполнять танцы с замысловатым рисунком. Например, добавление ссылки на ребенка к родителю выглядит так:
use std::cell::RefCell;
use std::rc::{Rc, Weak};
// The `derive` attribute automatically creates the implementation
// required to make this `struct` printable with `fmt::Debug`.
#[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());
}
Может показаться, что уж больно много букв надо сгруппировать и расставить в нужном порядке, неужели человек может осилить сие и не наломать дров? Хорошие новости в том, что в этом непростом деле всегда можно и нужно рассчитывать на прочное виртуальное плечо компилятора Rust, который будет жестко пресекать попытки небрежно отнестись к мелкому растопочному материалу.
Тип String
Без него дальше никак:
fn intro_string_type(){
let immutable_string = String::from("I'm immutable");
// immutable_string.push_str("!"); // ERR: ^ cannot borrow as mutable
let mut mutable_string = String::from("I'm mutable");
mutable_string.push_str("!");
println!("immutable_string: {}", immutable_string);
println!("mutable_string: {}", mutable_string);
}
- Примеры можно покрутить тут
- intro_string_type(), ого, змеиная нотация — так надо
- Переменные, объявленные через let, изменять нельзя
- Если надо менять используем let mut
- Вывод на печать осуществляется причудливой конструкцией println! (тут может возникнуть справедливое подозрение, что это не "обычная функция")
Лирика
SPL
Я думаю, Rust имеет потенциал находить отклик в сердцах многих. let как в Бейсике, {} как в Java, :: как в С++, объявление функции похоже на таковое из Go (только там func)
Владение и его передача присваиванием (Move)
Каждое значение имеет одного и только одного владельца-переменную. После операции присваивания переменная типа String перестает владеть своим бывшим значением, и ее нельзя больше использовать, даже напечатать нельзя:
fn once_assigned_string_may_not_be_used_anymore(){
let s1 = String::from("5");
let s2 = s1; // Ownership is moved here
// let s3 = s1; // ERR: value used here after move
// println!("{}, world!", s1); // ERR: value borrowed here after move
}
Для простых типов (primitive types), однако, значение копируется, а не передается, и для них многократное присваивание выглядит обычным образом:
fn primitives_are_copied(){
let i1 = 5;
let i2 = i1;
let i3 = i1;
}
- Есть trait (как бы interface) Copy, если тип его реализует, при присваивании/передаче в функцию/возврате значения происходит копирование
- Copy реализован для простых скалярных типов, а также для неких кортежей (tuples), при условии, что эти загадочные пока tuples содержат только типы, реализующие Copy
- Тип String не реализует Copy
- Из неочевидного: Copy несовместим с Drop (это где drop()). Несовместим, даже если не сам тип, а только его некоторые части реализуют Drop
Передача и возврат параметра по значению
При передаче переменной в функцию по значению происходит и передача владения (если тип не реализует интерфейс trait Copy):
fn use_str_by_value(s: String){
println!("{}", s);
}
fn passing_by_value_moves_ownership(){
let s1 = String::from("5");
use_str_by_value(s1);
// let s2 = s1; // ERR: value used here after move
}
Возврат значения (пример для расширения кругозора):
fn calculate_length(s: String) -> (String, usize) {
let length = s.len();
return (s, length)
}
fn calculate_length2(s: String) -> (String, usize) {
let length = s.len();
(s, length)
}
- Из функции можно вернуть несколько значений (тот самый кортеж, или tuple)
- usize означает целый беззнаковый тип, который вмещает указатель (the pointer-sized unsigned integer type)
- return можно не писать
- При возврате переменной "по значению" функция возвращает и владение (опять же, если не реализован Copy)
Ссылки и заимствование (References and Borrowing)
Необязательно брать значение во владение, его можно "занять" (borrow):
fn borrow(){
let mut s = String::from("hello");
let r1 = &s; // First immutable borrow occurs here
let r2 = &s; // Second immutable borrow occurs here
// let r3 = &mut s; // Err: mutable borrow occurs here
// r3.push_str(" world");
let r3 = &s; // Third immutable borrow occurs here
println!("{}, {}, and {}", r1, r2, r3);
}
- Занять можно как для чтения (&), так и для записи (&mut)
- Занять для чтения (immutable borrow) можно сколько угодно раз в "области видимости переменной" (variable scope)
- Занять для записи (mutable borrow) — только один раз
- Нельзя занимать одновременно для чтения и записи (все это похоже на read/write locks)
- Результат заимствования называется ссылкой (reference)
От перестановки строк из примера выше результат меняется и становится компилируемым:
fn borrow2(){
let mut s = String::from("hello");
let r1 = &s; // First immutable borrow occurs here
let r2 = &s; // Second immutable borrow occurs here
println!("{}, {}", r1, r2); // hello, hello
let r3 = &mut s; // Mutable borrow occurs here, r1 & r2 are not used anymore and out of scope
r3.push_str(" world");
println!("{}", r3); // hello world
}
- Видимость ссылки заканчивается там, где она последний раз используется
Висячие ссылки (Dangling References)
Компилятор Rust гарантирует, что эта проблема искоренена полностью. Компилятор откажется работать с попытками подвесить ссылки, в качестве причины отказа он приведет загадочную формулировку:
// fn dangling_reference() -> &String { // ERR: expected named lifetime parameter
// let s = String::from("hello");
// return &s
// }
Загадка lifetime parameter получит раскрытие в следующих главах.
Лирика
SPL
В свое время был удивлен, что в Go можно вернуть указатель на локальную переменную, и за это тебе ничего не будет:
//go:noinline
func ReturnPointerToLocal() *int{
a := 10
return &a
}
На деле хитрый компилятор в этом случае выделяет память в куче:
;*** main.go#9 >func ReturnPointerToLocal() *int{
...
;*** main.go#10 > a := 10
0x4a7a04 488d0575b00000 LEAQ type.*+43648(SB), AX
0x4a7a0b 48890424 MOVQ AX, 0(SP)
0x4a7a0f e84c5bf6ff CALL runtime.newobject(SB)
0x4a7a14 488b442408 MOVQ 0x8(SP), AX
0x4a7a19 48c7000a000000 MOVQ $0xa, 0(AX)
;*** main.go#11 > return &a
0x4a7a20 4889442420 MOVQ AX, 0x20(SP)
0x4a7a25 488b6c2410 MOVQ 0x10(SP), BP
0x4a7a2a 4883c418 ADDQ $0x18, SP
0x4a7a2e c3 RET
Срезы (Slices)
Очевидное:
fn string_slice(){
let s = String::from("Yandex");
let the_third_and_fourth_bytes_slice = &s[2..4]; // nd
let the_whole_string_slice = &s[..]; // Yandex
let first_two_bytes_slice = &s[..2]; // Ya
let from_the_third_byte_slice = &s[2..]; // ndex
}
Невероятное:
fn string_slice_multibyte(){
let s = String::from("y̆andex");
// let slice = &s[0..1]; // ERR: thread 'main' panicked at 'byte index 1 is not a char boundary...
// let slice = &s[0..2]; // ERR: thread 'main' panicked at 'byte index 1 is not a char boundary...
let valid_slice = &s[0..3];
println!("valid_slice: {}", valid_slice); // y̆
}
- Срезы строк можно делать только по труъ-unicode-границам, иначе паника
- Подробнее тут или here, кому что любо
Но то строки, с байтами ситуация попроще:
fn byte_slice(){
let s = String::from("y̆andex");
let bytes = s.as_bytes();
let slice = &bytes[0..2];
println!("{:?}, len: {}", slice, slice.len()); // [121, 204], len: 2
}
Теперь, собственно, про владение. Взятие среза "одалживает" всю последовательность:
fn slice_borrow_the_whole_sequence() {
let mut s = String::from("hello");
let first_two_bytes_slice = &s[..2]; // he
// s.push_str(" world"); // Err: cannot borrow `s` as mutable because it is also borrowed as immutable
println!("{}", first_two_bytes_slice);
}
- Не зря это делается при помощи &
- the book, раскрывая внутреннюю кухню, указывает, что в основе String лежат три значения (ptr, len, capacity), а slice довольствуется первыми двумя
Явное указание типов для slice не совсем очевидно:
fn return_slices(s: &String) -> (&str, &[u8]){
let bytes = s.as_bytes();
return (&s[..], &bytes[..])
}
- Срез строки имеет особенный тип &str
- Срез для as_bytes() имеет тип &[u8], что соответствует руководству: "slice type is &[T]"
Ссылки
- Примеры из статьи
- Как мы ржавели. История внедрения и обучения
- Understanding Ownership (the book)
- Понимание владения
- A half-hour to learn Rust
===========
Источник:
habr.com
===========
Похожие новости:
- Проект Tor представил реализацию на языке Rust, которая в будущем заменит вариант на Си
- [Информационная безопасность] Атака через поставщика или подрядчика глазами пентестера
- Вторая редакция патчей для ядра Linux с поддержкой языка Rust
- [Информационная безопасность, Программирование] Что под капотом у R-Vision Threat Intelligence Platform?
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.53.0: IntoIterator для массивов, "|" в шаблонах, Unicode-идентификаторы, поддержка имени HEAD-ветки в Cargo (перевод)
- Представлена библиотека Aya для создания eBPF-обработчиков на языке Rust
- [Информационная безопасность] Google-like система поиска уязвимостей IT Security Search — анонс вебинара
- [R, Rust] extendr: вызываем rust из R (и наоборот)
- [Настройка Linux, Rust, Разработка под Linux] Rust в ядре Linux (перевод)
- [Системное программирование, Rust] Rust — сохраняем безразмерные типы в статической памяти
Теги для поиска: #_rust, #_rust, #_rust
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 04:03
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 7 лет 2 месяца |
|
Nurked предложил оригинальный способ прочтения "the book" — читать главы надо не по порядку, а в последовательности [4, 3, 5, 6, 8, 4, 9, 7, 10, 4, 13, 17, 15, 16]. Можно пойти дальше. Мне представляется, что читать будет гораздо легче и быстрее, если выкинуть бо́льшую часть текста и заменить некоторые примеры. Ниже представлен "пробник" в виде сильно сокращенной главы 4. Чтение его подразумевает наличие опыта разработки на других языках — объяснения типа "чем отличается стек от кучи", естественно, попали под оптимизацию. ЛирикаSPLИз тех языков, с которыми я плотно работал, Rust ближе всего, КМК, к Go. Их роднит отсутствие "нормального ООП", отсутствие "нормальных исключений", концепция срезов (slice), наличие как объектов, так и ссылок/указателей на них, возможность возвращать несколько значений из функций, ну и, конечно, кросс-компиляция "из коробки". В Go пока нет обобщенных типов, но про них знают и их ждут. Поэтому опытного гофера ржавчиной не испугать.
Управление памятью Когда владелец (owner) покидает область видимости (variable scope), его "финализируют" через вызов drop(): {
let s = String::from("hello"); // s is valid from this point forward // do stuff with s } // This scope is now over, and s is no longer valid // Rust calls s.drop() automatically at the closing curly bracket. The book утверждает, что управление памятью осуществляется через "владение" с набором правил, которые компилятор проверяет во время компиляции программы. Полезно сразу иметь в виду (напомню — материал для опытных камикадзе), что есть некие: - Box<T> для распределения значений в куче (памяти)
- Rc<T> тип счётчика ссылок, который допускает множественное владение - Типы Ref<T> и RefMut<T>, доступ к которым осуществляется через тип RefCell<T>, который обеспечивает правила заимствования во время выполнения, вместо времени компиляции - Rust допускает утечки памяти, используя типы Rc<T> и RefCell<T> можно создавать связи, где элементы ссылаются друг на друга в цикле Короче, если память выделяется в куче, а полученные объекты ссылаются друг на друга — будь готов к граблям, засадам и к поиску утечек памяти. Воспоминание о будущемSPLВ силу исключительной важности вопроса забегу вперед.
Интересно, что сначала the book утверждает, мол, последствия зацикливания ссылок не очень страшны, а следующим предложением идет такой текст: However, if a more complex program allocated lots of memory in a cycle and held onto it for a long time, the program would use more memory than it needed and might overwhelm the system, causing it to run out of available memory.
use std::cell::RefCell;
use std::rc::{Rc, Weak}; // The `derive` attribute automatically creates the implementation // required to make this `struct` printable with `fmt::Debug`. #[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()); } Может показаться, что уж больно много букв надо сгруппировать и расставить в нужном порядке, неужели человек может осилить сие и не наломать дров? Хорошие новости в том, что в этом непростом деле всегда можно и нужно рассчитывать на прочное виртуальное плечо компилятора Rust, который будет жестко пресекать попытки небрежно отнестись к мелкому растопочному материалу. Тип String Без него дальше никак: fn intro_string_type(){
let immutable_string = String::from("I'm immutable"); // immutable_string.push_str("!"); // ERR: ^ cannot borrow as mutable let mut mutable_string = String::from("I'm mutable"); mutable_string.push_str("!"); println!("immutable_string: {}", immutable_string); println!("mutable_string: {}", mutable_string); }
ЛирикаSPLЯ думаю, Rust имеет потенциал находить отклик в сердцах многих. let как в Бейсике, {} как в Java, :: как в С++, объявление функции похоже на таковое из Go (только там func)
Владение и его передача присваиванием (Move) Каждое значение имеет одного и только одного владельца-переменную. После операции присваивания переменная типа String перестает владеть своим бывшим значением, и ее нельзя больше использовать, даже напечатать нельзя: fn once_assigned_string_may_not_be_used_anymore(){
let s1 = String::from("5"); let s2 = s1; // Ownership is moved here // let s3 = s1; // ERR: value used here after move // println!("{}, world!", s1); // ERR: value borrowed here after move } Для простых типов (primitive types), однако, значение копируется, а не передается, и для них многократное присваивание выглядит обычным образом: fn primitives_are_copied(){
let i1 = 5; let i2 = i1; let i3 = i1; }
Передача и возврат параметра по значению При передаче переменной в функцию по значению происходит и передача владения (если тип не реализует интерфейс trait Copy): fn use_str_by_value(s: String){
println!("{}", s); } fn passing_by_value_moves_ownership(){ let s1 = String::from("5"); use_str_by_value(s1); // let s2 = s1; // ERR: value used here after move } Возврат значения (пример для расширения кругозора): fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); return (s, length) } fn calculate_length2(s: String) -> (String, usize) { let length = s.len(); (s, length) }
Ссылки и заимствование (References and Borrowing) Необязательно брать значение во владение, его можно "занять" (borrow): fn borrow(){
let mut s = String::from("hello"); let r1 = &s; // First immutable borrow occurs here let r2 = &s; // Second immutable borrow occurs here // let r3 = &mut s; // Err: mutable borrow occurs here // r3.push_str(" world"); let r3 = &s; // Third immutable borrow occurs here println!("{}, {}, and {}", r1, r2, r3); }
От перестановки строк из примера выше результат меняется и становится компилируемым: fn borrow2(){
let mut s = String::from("hello"); let r1 = &s; // First immutable borrow occurs here let r2 = &s; // Second immutable borrow occurs here println!("{}, {}", r1, r2); // hello, hello let r3 = &mut s; // Mutable borrow occurs here, r1 & r2 are not used anymore and out of scope r3.push_str(" world"); println!("{}", r3); // hello world }
Висячие ссылки (Dangling References) Компилятор Rust гарантирует, что эта проблема искоренена полностью. Компилятор откажется работать с попытками подвесить ссылки, в качестве причины отказа он приведет загадочную формулировку: // fn dangling_reference() -> &String { // ERR: expected named lifetime parameter
// let s = String::from("hello"); // return &s // } Загадка lifetime parameter получит раскрытие в следующих главах. ЛирикаSPLВ свое время был удивлен, что в Go можно вернуть указатель на локальную переменную, и за это тебе ничего не будет:
//go:noinline
func ReturnPointerToLocal() *int{ a := 10 return &a } На деле хитрый компилятор в этом случае выделяет память в куче: ;*** main.go#9 >func ReturnPointerToLocal() *int{
... ;*** main.go#10 > a := 10 0x4a7a04 488d0575b00000 LEAQ type.*+43648(SB), AX 0x4a7a0b 48890424 MOVQ AX, 0(SP) 0x4a7a0f e84c5bf6ff CALL runtime.newobject(SB) 0x4a7a14 488b442408 MOVQ 0x8(SP), AX 0x4a7a19 48c7000a000000 MOVQ $0xa, 0(AX) ;*** main.go#11 > return &a 0x4a7a20 4889442420 MOVQ AX, 0x20(SP) 0x4a7a25 488b6c2410 MOVQ 0x10(SP), BP 0x4a7a2a 4883c418 ADDQ $0x18, SP 0x4a7a2e c3 RET Срезы (Slices) Очевидное: fn string_slice(){
let s = String::from("Yandex"); let the_third_and_fourth_bytes_slice = &s[2..4]; // nd let the_whole_string_slice = &s[..]; // Yandex let first_two_bytes_slice = &s[..2]; // Ya let from_the_third_byte_slice = &s[2..]; // ndex } Невероятное: fn string_slice_multibyte(){
let s = String::from("y̆andex"); // let slice = &s[0..1]; // ERR: thread 'main' panicked at 'byte index 1 is not a char boundary... // let slice = &s[0..2]; // ERR: thread 'main' panicked at 'byte index 1 is not a char boundary... let valid_slice = &s[0..3]; println!("valid_slice: {}", valid_slice); // y̆ }
Но то строки, с байтами ситуация попроще: fn byte_slice(){
let s = String::from("y̆andex"); let bytes = s.as_bytes(); let slice = &bytes[0..2]; println!("{:?}, len: {}", slice, slice.len()); // [121, 204], len: 2 } Теперь, собственно, про владение. Взятие среза "одалживает" всю последовательность: fn slice_borrow_the_whole_sequence() {
let mut s = String::from("hello"); let first_two_bytes_slice = &s[..2]; // he // s.push_str(" world"); // Err: cannot borrow `s` as mutable because it is also borrowed as immutable println!("{}", first_two_bytes_slice); }
Явное указание типов для slice не совсем очевидно: fn return_slices(s: &String) -> (&str, &[u8]){
let bytes = s.as_bytes(); return (&s[..], &bytes[..]) }
Ссылки
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 04:03
Часовой пояс: UTC + 5