Умни указатели
08 ноември 2022
Преговор
Преговор
- Lifetimes (
'static
,'a
и т.н.)
?Sized
???
Sized
е ✨магически✨ trait
?Sized
???
Sized
е ✨магически✨ trait- Имплементира се автоматично от типове, чиито размер се знае at compile-time
?Sized
???
Sized
е ✨магически✨ trait- Имплементира се автоматично от типове, чиито размер се знае at compile-time
- Примерно:
u8
,Vec<T>
,&T
?Sized
???
- Типове, които не са
Sized
:[T]
,dyn Trait
,str
?Sized
???
- Типове, които не са
Sized
:[T]
,dyn Trait
,str
- Винаги стоят зад някакъв pointer/reference:
&[T]
,&dyn Trait
,&str
?Sized
???
- Типове, които не са
Sized
:[T]
,dyn Trait
,str
- Винаги стоят зад някакъв pointer/reference:
&[T]
,&dyn Trait
,&str
- Reference-а в този случай е "fat pointer", който държи не само указател, но и размера на парчето данни, което има този тип.
?Sized
???
- Типове, които не са
Sized
:[T]
,dyn Trait
,str
- Винаги стоят зад някакъв pointer/reference:
&[T]
,&dyn Trait
,&str
- Reference-а в този случай е "fat pointer", който държи не само указател, но и размера на парчето данни, което има този тип.
- Забележете, че
[u32]
не еSized
, но[u32; 5]
е. Защото размера на данните се съдържа в конкретния тип.
?Sized
???
?Sized
означава, че типа не е нужно да имплементира Sized.
// Използваем само с тип, който имплементира Sized:
fn foo<T>() {}
// Използваем с тип, който *може* да имплементира Sized,
// но не е *нужно*:
fn bar<T: ?Sized>() {}
Особено ограничение, понеже разширява броя типове, които могат да се приемат, вместо да го стеснява.
Защо? Защото ако една стойност не е Sized, компилатора не може да я алокира на стека. Така че е доста добра идея да присъства като автоматичен trait bound.
?Sized
???
fn foo<T>(t: T) {} // -> totally fine, T е Sized
fn bar<T: ?Sized>(t: &T) {} // -> totally fine, &T е Sized
// fn bar<T: ?Sized>(t: T) {} // -> невъзможно, защото как ще се алокира t?
Smart pointers
&T
Най-глупавия указател, но важен за целите на сравнението
&T
- Най-простия начин да реферираме към памет, където и да е в паметта (който не изисква
unsafe
)
&T
- Най-простия начин да реферираме към памет, където и да е в паметта (който не изисква
unsafe
) - Няма ownership -- нещо друго трябва да е owner на тази памет
&T
- Най-простия начин да реферираме към памет, където и да е в паметта (който не изисква
unsafe
) - Няма ownership -- нещо друго трябва да е owner на тази памет
- Нужда от описване на lifetimes при употреба във функции и структури
&T
- Най-простия начин да реферираме към памет, където и да е в паметта (който не изисква
unsafe
) - Няма ownership -- нещо друго трябва да е owner на тази памет
- Нужда от описване на lifetimes при употреба във функции и структури
- Позволяват максимална ефективност при достъп -- директно адресираме памет
&T: Директен достъп до памет
let potato = String::from("
Любов, любов, варен картоф,
разрежеш го, а той суров.
");
let lines = potato.
trim().
lines().
map(|l| l.trim());
for line in lines {
println!("{}", line);
}
Любов, любов, варен картоф, разрежеш го, а той суров.
fn main() { let potato = String::from(" Любов, любов, варен картоф, разрежеш го, а той суров. "); let lines = potato. trim(). lines(). map(|l| l.trim()); for line in lines { println!("{}", line); } }
В горния пример има само алокация на първия String
, останалите методи -- trim
, lines
, map
, само алокират малки стойности на стека.
Box
Reference + ownership!
Box
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
b = 5
fn main() { let b = Box::new(5); println!("b = {}", b); }
Box
- Също прост -- сочи към парче памет, само и единствено в heap-а
Box
- Също прост -- сочи към парче памет, само и единствено в heap-а
- Държи ownership над данните си, което значи, че няма нужда да се грижим за lifetimes
Box
- Също прост -- сочи към парче памет, само и единствено в heap-а
- Държи ownership над данните си, което значи, че няма нужда да се грижим за lifetimes
- (Но алокира нова стойност на heap-а, което може да е малко по-неефективно от reference към нещо на стека.)
- (Това обикновено не е практически проблем --
Vec
,String
и т.н. си алокират неща на heap-а)
Box
- Също прост -- сочи към парче памет, само и единствено в heap-а
- Държи ownership над данните си, което значи, че няма нужда да се грижим за lifetimes
- (Но алокира нова стойност на heap-а, което може да е малко по-неефективно от reference към нещо на стека.)
- (Това обикновено не е практически проблем --
Vec
,String
и т.н. си алокират неща на heap-а) - Донякъде магически -- няма как да си направим наш
Box
, защото няма как да укажем на компилатора "алокирай това на heap-а".
Box
- Също прост -- сочи към парче памет, само и единствено в heap-а
- Държи ownership над данните си, което значи, че няма нужда да се грижим за lifetimes
- (Но алокира нова стойност на heap-а, което може да е малко по-неефективно от reference към нещо на стека.)
- (Това обикновено не е практически проблем --
Vec
,String
и т.н. си алокират неща на heap-а) - Донякъде магически -- няма как да си направим наш
Box
, защото няма как да укажем на компилатора "алокирай това на heap-а". - Много условно казано, горе-долу, донякъде, може да си представите, че:
String
~~Box<str>
Vec<T>
~~Box<[T]>
Box
fn main() {
let x = Box::new(3);
let y = Box::new(5);
println!("{}", x + y);
}
error[E0369]: cannot add `Box<{integer}>` to `Box<{integer}>` --> src/bin/main_81c11fbe09e1559f40de38f65b4627444769a9b5.rs:5:22 | 5 | println!("{}", x + y); | - ^ - Box<{integer}> | | | Box<{integer}> For more information about this error, try `rustc --explain E0369`. error: could not compile `rust` due to previous error
fn main() { let x = Box::new(3); let y = Box::new(5); println!("{}", x + y); }
Box
fn main() {
let x = Box::new(3);
let y = Box::new(5);
println!("{}", *x + *y);
let x = &3;
let y = &5;
println!("{}", *x + *y);
}
8 8
fn main() { let x = Box::new(3); let y = Box::new(5); println!("{}", *x + *y); let x = &3; let y = &5; println!("{}", *x + *y); }
(Note: това не е специално за Box -- ще видим как работи *
след малко)
Box
А за какво ни е всъщност?
Box
Linked list без Box
#[derive(Debug)]
enum List {
Nil,
Cons(i32, List),
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
println!("{:#?}", list);
}
error[E0072]: recursive type `List` has infinite size --> src/bin/main_8ba92f92a6708ff40e65364f4344a56ecfa46195.rs:2:1 | 2 | enum List { | ^^^^^^^^^ recursive type has infinite size 3 | Nil, 4 | Cons(i32, List), | ---- recursive without indirection | help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable | 4 | Cons(i32, Box<List>), | ++++ + For more information about this error, try `rustc --explain E0072`. error: could not compile `rust` due to previous error
#[derive(Debug)] enum List { Nil, Cons(i32, List), } use List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil))); println!("{:#?}", list); }
Box
Box има фиксиран размер на стека, така че няма проблеми.
#[derive(Debug)]
enum List {
Nil,
Cons(i32, Box<List>),
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
println!("{:?}", list);
}
Cons(1, Cons(2, Cons(3, Nil)))
#[derive(Debug)] enum List { Nil, Cons(i32, Box), } use List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); println!("{:?}", list); }
Box
Можем ли вместо Box
да ползваме &
? Kinda:
#[derive(Debug)]
enum List<'a> {
Nil,
Cons(i32, &'a List<'a>),
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, &Cons(2, &Nil));
println!("{:?}", list);
}
Cons(1, Cons(2, Nil))
#[derive(Debug)] enum List<'a> { Nil, Cons(i32, &'a List<'a>), } use List::{Cons, Nil}; fn main() { let list = Cons(1, &Cons(2, &Nil)); println!("{:?}", list); }
Box
Това работи, но не можем да местим тази стойност:
#[derive(Debug)]
enum List<'a> {
Nil,
Cons(i32, &'a List<'a>),
}
use List::{Cons, Nil};
fn return_list<'a>(x: i32, y: i32) -> List<'a> {
let list1 = Nil;
let list2 = Cons(y, &list1);
Cons(x, &list2)
}
fn main() {
println!("{:?}", return_list(1, 2));
}
error[E0515]: cannot return value referencing local variable `list2` --> src/bin/main_784ae169fd7da4be02bf7925fdc77105c9df81ec.rs:12:5 | 12 | Cons(x, &list2) | ^^^^^^^^------^ | | | | | `list2` is borrowed here | returns a value referencing data owned by the current function error[E0515]: cannot return value referencing local variable `list1` --> src/bin/main_784ae169fd7da4be02bf7925fdc77105c9df81ec.rs:12:5 | 11 | let list2 = Cons(y, &list1); | ------ `list1` is borrowed here 12 | Cons(x, &list2) | ^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function For more information about this error, try `rustc --explain E0515`. error: could not compile `rust` due to 2 previous errors
#[derive(Debug)] enum List<'a> { Nil, Cons(i32, &'a List<'a>), } use List::{Cons, Nil}; fn return_list<'a>(x: i32, y: i32) -> List<'a> { let list1 = Nil; let list2 = Cons(y, &list1); Cons(x, &list2) } fn main() { println!("{:?}", return_list(1, 2)); }
Box
Никакъв проблем ако ползваме Box
, защото Cons(1, Box::new(...))
си държи ownership:
#[derive(Debug)]
enum List {
Nil,
Cons(i32, Box<List>),
}
use List::{Cons, Nil};
fn return_list(x: i32, y: i32) -> List {
let list1 = Box::new(Nil);
let list2 = Cons(y, list1);
Cons(x, Box::new(list2))
}
fn main() {
println!("{:?}", return_list(1, 2));
}
Cons(1, Cons(2, Nil))
#[derive(Debug)] enum List { Nil, Cons(i32, Box), } use List::{Cons, Nil}; fn return_list(x: i32, y: i32) -> List { let list1 = Box::new(Nil); let list2 = Cons(y, list1); Cons(x, Box::new(list2)) } fn main() { println!("{:?}", return_list(1, 2)); }
Box
Trait objects (преговор)
Ако имаме trait Stuff
, &dyn Stuff
представлява какъвто и да е обект имплементиращ trait-а.
fn to_json(value: &dyn ToJson) -> String {
value.to_json()
}
fn main() {
let trait_object: &dyn ToJson = &5;
println!("{}", to_json(trait_object));
println!("{}", to_json(&5));
println!("{}", to_json(&5 as &dyn ToJson));
}
5 5 5
trait ToJson { fn to_json(&self) -> String; } impl ToJson for i32 { fn to_json(&self) -> String { format!("{}", self) } } fn to_json(value: &dyn ToJson) -> String { value.to_json() } fn main() { let trait_object: &dyn ToJson = &5; println!("{}", to_json(trait_object)); println!("{}", to_json(&5)); println!("{}", to_json(&5 as &dyn ToJson)); }
Box
Trait objects
fn vec_of_things<'a>() -> Vec<&'a dyn Display> {
let x = 123;
vec![&x, &3.14, &"foobar"]
}
error[E0515]: cannot return value referencing local variable `x` --> src/bin/main_6ccf239cdd09201f91ae142543011bacb499f05c.rs:4:5 | 4 | vec![&x, &3.14, &"foobar"] | ^^^^^--^^^^^^^^^^^^^^^^^^^ | | | | | `x` is borrowed here | returns a value referencing data owned by the current function | = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0515`. error: could not compile `rust` due to previous error
use std::fmt::Display; fn vec_of_things<'a>() -> Vec<&'a dyn Display> { let x = 123; vec![&x, &3.14, &"foobar"] } fn main() {}
Box
Trait objects
fn vec_of_things() -> Vec<Box<dyn Display>> {
let x = 123;
vec![Box::new(x), Box::new(3.14), Box::new("foobar")]
}
fn main() {
for thing in vec_of_things() {
println!("{}", thing);
}
}
123 3.14 foobar
use std::fmt::Display; fn vec_of_things() -> Vec> { let x = 123; vec![Box::new(x), Box::new(3.14), Box::new("foobar")] } fn main() { for thing in vec_of_things() { println!("{}", thing); } }
Box
Box<Error>
го споменахме вече -- ако ни мързи да правим error handling
fn get_x() -> Result<i32, std::io::Error> { Ok(3) }
fn get_y() -> Result<i32, std::fmt::Error> { Ok(5) }
fn foo() -> Result<i32, Box<dyn std::error::Error>> {
let x = get_x()?;
let y = get_y()?;
Ok(x + y)
}
fn main() {
println!("{:?}", foo());
}
Ok(8)
fn get_x() -> Result{ Ok(3) } fn get_y() -> Result { Ok(5) } fn foo() -> Result > { let x = get_x()?; let y = get_y()?; Ok(x + y) } fn main() { println!("{:?}", foo()); }
Това рядко ще е проблем откъм performance.
Nightly Rust
- В nightly rust има експериментален синтаксис
box
(който е много стар вече и не се знае дали някога ще го стабилизират)
Nightly Rust
- В nightly rust има експериментален синтаксис
box
(който е много стар вече и не се знае дали някога ще го стабилизират) - Можете да си инсталирате nightly rust с
rustup install nightly
- Можете да си тествате код с
cargo +nightly run
- Можете и да го направите default-ен (но по-добре недейте)
Box
Nightly features
Има специален keyword : box
за създаване на Box smart pointer-и
let x = Box::new(5);
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// Може да се напише така:
let x = box 5;
let list = Cons(1, box Cons(2, box Cons(3, box Nil)));
Box
Nightly features
За да може да използвате този 'feature', трябва да го оповестите така в началото на програмата си:
#![feature(box_syntax)]
struct Heart {
owner: &'static str,
}
fn main() {
let heart_shaped_box = box Heart { owner: "Kurt" };
}
Box
Nightly features
Ключовата дума box
е мнооого полезна при pattern matching! Пример:
#[derive(Clone, Debug, PartialEq)]
pub enum Term {
True,
False,
If(Box<Term>, Box<Term>, Box<Term>),
Value
}
#[derive(Clone, Debug, PartialEq)] pub enum Term { True, False, If(Box, Box , Box ), Value } fn main() {}
Типа Box<Term>
не може да се pattern-match-не по компоненти -- вътрешността му е private.
Box
Nightly features
Използвайки feature(box_patterns)
, можем да pattern-match-ваме Box<Term>
:
#![feature(box_syntax)]
#![feature(box_patterns)]
fn one_step_eval(t: Term) -> Result<Term, String> {
match t {
Term::If(box Term::True, t2, _) => Ok(*t2),
Term::If(box Term::False, _, t3) => Ok(*t3),
Term::If(t1, t2, t3) => Ok(Term::If(box one_step_eval(*t1)?, t2, t3)),
any => Err(format!("Term can't be evaluated : {:?}", any))
}
}
Deref
Как работи *
при нормалните references?
let mut x = 5;
{
let y = &mut x;
*y += 1;
println!("y = {}", y);
}
println!("x = {}", x);
y = 6 x = 6
fn main() { let mut x = 5; { let y = &mut x; *y += 1; println!("y = {}", y); } println!("x = {}", x); }
Може да достъпим стойността зад reference-а чрез *
и да извършим някаква операция върху нея.
Deref
let x = 5;
{
let mut y = Box::new(x);
*y += 1;
println!("y = {}", y);
}
println!("x = {}", x);
y = 6 x = 5
fn main() { let x = 5; { let mut y = Box::new(x); *y += 1; println!("y = {}", y); } println!("x = {}", x); }
Това работи и за Box
-нати стойности, защото за типа Box
е имплементиран trait-а Deref
.
Deref
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
Нужно е ?Sized
, понеже така може да имплементираме Deref
за не-Sized тип. От стандартната библиотека:
Deref
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
Нужно е ?Sized
, понеже така може да имплементираме Deref
за не-Sized тип. От стандартната библиотека:
impl ops::Deref for String {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}
Тук типа Target
е str
, който не е Sized
. Но &str
тотално е Sized
и можем да върнем &Target
като резултат.
Метода deref
не връща директно Target
, защото това би ни дало ownership над стойността и в случая на Box<T>
, това би преместило стойността и би направило инстанцията неизползваема.
DerefMut
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Забележете липсата на декларация на Target
-- на практика се случва <Self as Deref>::Target
.
Deref
use std::ops::Deref;
struct Mp3 {
audio: Vec<u8>,
artist: Option<String>,
title: Option<String>,
}
impl Deref for Mp3 {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> {
&self.audio
}
}
#![allow(dead_code)] use std::ops::Deref; struct Mp3 { audio: Vec, artist: Option , title: Option , } impl Deref for Mp3 { type Target = Vec ; fn deref(&self) -> &Vec { &self.audio } } fn main() {}
Deref
fn main() {
let appropriately_named_song = Mp3 {
audio: vec![1, 2, 3],
artist: Some(String::from("Poets of the Fall")),
title: Some(String::from("Carnival of Rust")),
};
assert_eq!(vec![1, 2, 3], *appropriately_named_song);
}
#![allow(dead_code)] use std::ops::Deref; struct Mp3 { audio: Vec, artist: Option , title: Option , } impl Deref for Mp3 { type Target = Vec ; fn deref(&self) -> &Vec { &self.audio } } fn main() { let appropriately_named_song = Mp3 { audio: vec![1, 2, 3], artist: Some(String::from("Poets of the Fall")), title: Some(String::from("Carnival of Rust")), }; assert_eq!(vec![1, 2, 3], *appropriately_named_song); }
Deref
Как се "разгъва" този код от компилатора:
*appropriately_named_song // -> Компилатора вижда Deref &Mp3 -> &Vec
*(appropriately_named_song.deref())
// Имплементацията връща &self.audio
fn deref(&self) -> &Vec<u8> {
&self.audio
}
// Разпънатия код е *& от вътрешното `audio`
*(&appropriately_named_song.audio)
Deref
- Позволява свеждане на нещо-което-работи-като-указател до истински указател
Deref
- Позволява свеждане на нещо-което-работи-като-указател до истински указател
- Извиква се автоматично от
*
: Когато напишем*foo
, компилатора го свежда до*Deref::deref(&foo)
.
Deref
- Позволява свеждане на нещо-което-работи-като-указател до истински указател
- Извиква се автоматично от
*
: Когато напишем*foo
, компилатора го свежда до*Deref::deref(&foo)
. - Извиква се автоматично от
*=
: Когато напишем*foo = ...
, компилатора го свежда до*(DerefMut::deref_mut(&mut foo)) = ...
.
Deref
- Позволява свеждане на нещо-което-работи-като-указател до истински указател
- Извиква се автоматично от
*
: Когато напишем*foo
, компилатора го свежда до*Deref::deref(&foo)
. - Извиква се автоматично от
*=
: Когато напишем*foo = ...
, компилатора го свежда до*(DerefMut::deref_mut(&mut foo)) = ...
. - Извиква се автоматично и в няколко други ситуации, които ще видим след няколко слайда
Deref
- Позволява свеждане на нещо-което-работи-като-указател до истински указател
- Извиква се автоматично от
*
: Когато напишем*foo
, компилатора го свежда до*Deref::deref(&foo)
. - Извиква се автоматично от
*=
: Когато напишем*foo = ...
, компилатора го свежда до*(DerefMut::deref_mut(&mut foo)) = ...
. - Извиква се автоматично и в няколко други ситуации, които ще видим след няколко слайда
- Дефиниран е автоматично за всички references. От стандартната библиотека:
impl<'a, T: ?Sized> Deref for &'a T {
type Target = T;
fn deref(&self) -> &T { *self }
}
Въпрос: защо *self
?
Deref
- Позволява свеждане на нещо-което-работи-като-указател до истински указател
- Извиква се автоматично от
*
: Когато напишем*foo
, компилатора го свежда до*Deref::deref(&foo)
. - Извиква се автоматично от
*=
: Когато напишем*foo = ...
, компилатора го свежда до*(DerefMut::deref_mut(&mut foo)) = ...
. - Извиква се автоматично и в няколко други ситуации, които ще видим след няколко слайда
- Дефиниран е автоматично за всички references. От стандартната библиотека:
impl<'a, T: ?Sized> Deref for &'a T {
type Target = T;
fn deref(&self) -> &T { *self }
}
Въпрос: защо *self
?
Защото &self
е от тип &Self
, което в случая е &&T
. Така че *self
е от тип &T
!
DerefMut
Така работи Box
-- викаме му *y += 1
:
//
//
fn main() {
let mut y: Box<u32> = Box::new(5); // Note: mutable
*y += 1;
println!("{}", y);
}
6
// fn main() { let mut y: Box= Box::new(5); // Note: mutable *y += 1; println!("{}", y); }
DerefMut
Това се превежда до *DerefMut::deref_mut(&mut y) += 1
use std::ops::DerefMut;
fn main() {
let mut y: Box<u32> = Box::new(5); // Note: mutable
*(DerefMut::deref_mut(&mut y)) += 1; //*(&mut u32)
println!("{}", y);
}
6
use std::ops::DerefMut; fn main() { let mut y: Box= Box::new(5); // Note: mutable *(DerefMut::deref_mut(&mut y)) += 1; //*(&mut u32) println!("{}", y); }
Deref
deref coercion
Автоматично се вика Deref
не само при *
, но и при викане на функции. Примерно, можем да извикаме директно .audio.as_slice()
:
fn compress_mp3(audio: &[u8]) -> Vec<u8> {
// ...
}
compress_mp3(appropriately_named_song.audio.as_slice())
Deref
deref coercion
Но можем и да подадем нещо, на което Deref
ще докара правилния тип:
fn compress_mp3(audio: &[u8]) -> Vec<u8> {
// ...
}
compress_mp3(appropriately_named_song.audio.as_slice())
// &Vec<u8> -> &[u8]
compress_mp3(&appropriately_named_song.audio)
// &Mp3 -> &Vec<u8>
compress_mp3(&appropriately_named_song)
// Става и без викане на функция:
let song_bytes: &[u8] = &appropriately_named_song;
Deref
deref coercion
fn compress_mp3(audio: &[u8]) -> Vec<u8> {
// ...
}
// Еквивалентни:
compress_mp3(&appropriately_named_song)
compress_mp3(&appropriately_named_song.deref().deref())
Deref
deref coercion
&[u32; 5]
→&[u32]
&Vec<u32>
→&[u32]
&String
→&str
Deref
deref coercion
- От
&T
до&U
когатоT: Deref<Target=U>
Deref
deref coercion
- От
&T
до&U
когатоT: Deref<Target=U>
- От
&mut T
до&mut U
когатоT: DerefMut<Target=U>
Deref
deref coercion
- От
&T
до&U
когатоT: Deref<Target=U>
- От
&mut T
до&mut U
когатоT: DerefMut<Target=U>
- От
&mut T
до&U
когатоT: Deref<Target=U>
Deref
deref coercion
- От
&T
до&U
когатоT: Deref<Target=U>
- От
&mut T
до&mut U
когатоT: DerefMut<Target=U>
- От
&mut T
до&U
когатоT: Deref<Target=U>
- (Вижте документацията на String, където ще намерите "Methods from
Deref<Target = str>
") - (https://doc.rust-lang.org/std/string/struct.String.html#deref-methods)
Deref
deref coercion
- От
&T
до&U
когатоT: Deref<Target=U>
- От
&mut T
до&mut U
когатоT: DerefMut<Target=U>
- От
&mut T
до&U
когатоT: Deref<Target=U>
- (Вижте документацията на String, където ще намерите "Methods from
Deref<Target = str>
") - (https://doc.rust-lang.org/std/string/struct.String.html#deref-methods)
- Side note: Освен това има и автоматичен conversion от
&mut T
до&T
(това се нарича просто "coercion", не "deref coercion")
Deref
- Цялата тая имплицитност е много удобна, но… Не прекалявайте.
Deref
се ползва специфично за smart pointer-и.
Deref
- Цялата тая имплицитност е много удобна, но… Не прекалявайте.
Deref
се ползва специфично за smart pointer-и. Mp3
е просто пример, не би бил много добра идея на практика. Автоматичен дереф може да е доста объркващ в случай на грешка.
Rc
Reference counter
Reference counting
use std::rc::Rc;
fn main() {
let first = Rc::new(String::from("foobar"));
let second = Rc::clone(&first);
println!("{}", first);
println!("{}", second);
}
foobar foobar
use std::rc::Rc; fn main() { let first = Rc::new(String::from("foobar")); let second = Rc::clone(&first); println!("{}", first); println!("{}", second); }
Reference counting
let a = Rc::new(3);
let b = Rc::new(5);
println!("{}", *a + *b);
8
use std::rc::Rc; fn main() { let a = Rc::new(3); let b = Rc::new(5); println!("{}", *a + *b); }
Reference counting
Rc::new
алокира подадената си стойност на heap-а, както Box
Reference counting
Rc::new
алокира подадената си стойност на heap-а, както BoxRc::clone
дава новоRc
, което вътрешно сочи до същата стойност, но има ownership, също както Box!
Reference counting
Rc::new
алокира подадената си стойност на heap-а, както BoxRc::clone
дава новоRc
, което вътрешно сочи до същата стойност, но има ownership, също както Box!- Всяко викане на
clone
, вместо да клонира потенциално голямата стойност, просто връща фасада към тази стойност и увеличава един counter.
Reference counting
Rc::new
алокира подадената си стойност на heap-а, както BoxRc::clone
дава новоRc
, което вътрешно сочи до същата стойност, но има ownership, също както Box!- Всяко викане на
clone
, вместо да клонира потенциално голямата стойност, просто връща фасада към тази стойност и увеличава един counter. - Когато
Rc
-то се drop-не (деструктира), counter-а пада с 1. Като падне до 0, стойността се деалокира.
Reference counting
Проблем: стойността е read-only:
let mut a = Rc::new(3);
*a = 5;
println!("{:?}", a);
error[E0594]: cannot assign to data in an `Rc` --> src/bin/main_daef8d7d55ceabff0dc2ebe0ac97186bb40b3b39.rs:5:1 | 5 | *a = 5; | ^^^^^^ cannot assign | = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<i32>` For more information about this error, try `rustc --explain E0594`. error: could not compile `rust` due to previous error
use std::rc::Rc; fn main() { let mut a = Rc::new(3); *a = 5; println!("{:?}", a); }
Reference counting
Rc
не ни позволява да взимаме mutable reference към пазената стойност
Reference counting
Rc
не ни позволява да взимаме mutable reference към пазената стойност- това би нарушило ограничението за един
&mut T
/ много&T
Reference counting
Rc
не ни позволява да взимаме mutable reference към пазената стойност- това би нарушило ограничението за един
&mut T
/ много&T
- но въпреки това има начини да модифицираме пазената стойност
Copy-on-write
Cow
Copy on Write
impl<T> Rc<T> where T: Clone {
fn make_mut(this: &mut Rc<T>) -> &mut T
}
Cow
Copy on Write
impl<T> Rc<T> where T: Clone {
fn make_mut(this: &mut Rc<T>) -> &mut T
}
- ако сме единствения собственик модифицираме директно пазената стойност
Cow
Copy on Write
impl<T> Rc<T> where T: Clone {
fn make_mut(this: &mut Rc<T>) -> &mut T
}
- ако сме единствения собственик модифицираме директно пазената стойност
- ако не първо си правим копие на стойността и модифицираме копието
Cow
use std::rc::Rc;
fn main() {
let mut a = Rc::new(3);
*Rc::make_mut(&mut a) = 5;
println!("a: {}", a);
}
a: 5
use std::rc::Rc; fn main() { let mut a = Rc::new(3); *Rc::make_mut(&mut a) = 5; println!("a: {}", a); }
Cow
use std::rc::Rc;
fn main() {
let mut a = Rc::new(3);
let b = Rc::clone(&a);
// Дотук a и b сочат към една и съща стойност в паметта
{
let temp_ref = Rc::make_mut(&mut a);
// Връщаме &mut към copy-on-write стойност
*temp_ref = 5;
}
// Вече a и b сочат към различни стойности
println!("a: {}", a);
println!("b: {}", b);
}
a: 5 b: 3
use std::rc::Rc; fn main() { let mut a = Rc::new(3); let b = Rc::clone(&a); // Дотук a и b сочат към една и съща стойност в паметта { let temp_ref = Rc::make_mut(&mut a); // Връщаме &mut към copy-on-write стойност *temp_ref = 5; } // Вече a и b сочат към различни стойности println!("a: {}", a); println!("b: {}", b); }
Друг вариант: Internal mutability
- пазим състояние (internal state), невидимо за външния свят
Друг вариант: Internal mutability
- пазим състояние (internal state), невидимо за външния свят
- искаме да модифицираме това състояние в методи, които са логически immutable
Друг вариант: Internal mutability
- пазим състояние (internal state), невидимо за външния свят
- искаме да модифицираме това състояние в методи, които са логически immutable
- това се нарича internal mutability
Cell, RefCell
Internal mutability
use std::cell::Cell;
fn main() {
// забележете, че няма `mut`
let cell = Cell::new(10);
println!("{}", cell.get());
cell.set(42);
println!("{}", cell.get());
}
10 42
use std::cell::Cell; fn main() { // забележете, че няма `mut` let cell = Cell::new(10); println!("{}", cell.get()); cell.set(42); println!("{}", cell.get()); }
Internal mutability
Cell
- използва се предимно за
Copy
типове -- някои функционалности работят само заCopy
Internal mutability
Cell
- използва се предимно за
Copy
типове -- някои функционалности работят само заCopy
get
прави копие на пазената стойност (искаCopy
)set
презаписва пазената стойност с новатаreplace
записва нова стойност и връща старата
Internal mutability
Cell
- използва се предимно за
Copy
типове -- някои функционалности работят само заCopy
get
прави копие на пазената стойност (искаCopy
)set
презаписва пазената стойност с новатаreplace
записва нова стойност и връща старатаinto_inner
ще консумираCell
-а и директно ще върне вътрешната стойност
Internal mutability
Cell
- използва се предимно за
Copy
типове -- някои функционалности работят само заCopy
get
прави копие на пазената стойност (искаCopy
)set
презаписва пазената стойност с новатаreplace
записва нова стойност и връща старатаinto_inner
ще консумираCell
-а и директно ще върне вътрешната стойност- не можем да вземем референция (
&
/&mut
) към вътрешната стойност, само към обвиващияCell
Internal mutability
RefCell
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("foo")); // отново няма `mut`
println!("{}", cell.borrow()); // -> Ref<String>
cell.borrow_mut().push_str("bar"); // -> RefMut<String>
println!("{}", cell.borrow()); // -> Ref<String>
}
foo foobar
use std::cell::RefCell; fn main() { let cell = RefCell::new(String::from("foo")); // отново няма `mut` println!("{}", cell.borrow()); // -> Refcell.borrow_mut().push_str("bar"); // -> RefMut println!("{}", cell.borrow()); // -> Ref }
Internal mutability
RefCell
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(String::from("foo")); // отново няма `mut`
let mut first = cell.borrow_mut();
let mut second = cell.borrow_mut(); // BOOM!
}
thread 'main' panicked at 'already borrowed: BorrowMutError', src/bin/main_45bb39151537827e2a223fcdd3995ba26df346ef.rs:9:27 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
#![allow(unused_variables)] #![allow(unused_mut)] use std::cell::RefCell; fn main() { let cell = RefCell::new(String::from("foo")); // отново няма `mut` let mut first = cell.borrow_mut(); let mut second = cell.borrow_mut(); // BOOM! }
Internal mutability
RefCell
- Runtime borrow checker
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
borrow()
ще върне структура от типRef
, която има deref до&T
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
borrow()
ще върне структура от типRef
, която има deref до&T
borrow_mut()
ще върне структура от типRefMut
, която има deref до&mut T
Internal mutability
RefCell
- Runtime borrow checker
- помни колко immutable и mutable референции е раздал
borrow()
ще върне структура от типRef
, която има deref до&T
borrow_mut()
ще върне структура от типRefMut
, която има deref до&mut T
- ако не можем да вземем референция по стандартните правила на borrow checker-а ще получим
panic!
вместо компилаторна грешка
Internal mutability
Често Cell
и RefCell
се използват в комбинация с Rc
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let first = Rc::new(RefCell::new(String::from("foo")));
let second = Rc::clone(&first);
first.borrow_mut().push_str("bar");
println!("{}", second.borrow());
}
foobar
use std::cell::RefCell; use std::rc::Rc; fn main() { let first = Rc::new(RefCell::new(String::from("foo"))); let second = Rc::clone(&first); first.borrow_mut().push_str("bar"); println!("{}", second.borrow()); }
Cell, RefCell
- Cell се използва предимно за
Copy
типове, RefCell - не само
Cell, RefCell
- Cell се използва предимно за
Copy
типове, RefCell - не само - Cell връща стойности, RefCell връща
Ref
-ове иRefMut
-ове (как работи това откъм lifetimes? Повече в следващи лекции)
Cell, RefCell
- Cell се използва предимно за
Copy
типове, RefCell - не само - Cell връща стойности, RefCell връща
Ref
-ове иRefMut
-ове (как работи това откъм lifetimes? Повече в следващи лекции) - Cell не хвърля panics, RefCell хвърля
Cell, RefCell
- Cell се използва предимно за
Copy
типове, RefCell - не само - Cell връща стойности, RefCell връща
Ref
-ове иRefMut
-ове (как работи това откъм lifetimes? Повече в следващи лекции) - Cell не хвърля panics, RefCell хвърля
- Заобикалят "нормалните" правила, съответно ги избягвайте (понякога просто са полу-необходими).
- Документацията потвърждава: "… interior mutability is something of a last resort."
Обратно към Rc
Weak reference
Какво правим, когато структурата ни може да има цикли?
struct TreeNode {
value: u32,
parent: Option<Rc<RefCell<TreeNode>>>,
children: Vec<Rc<RefCell<TreeNode>>>,
}
impl TreeNode {
fn new(value: u32, parent: Option<Rc<RefCell<TreeNode>>>) -> Rc<RefCell<TreeNode>> {
Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] }))
}
}
#![allow(dead_code)] use std::rc::Rc; use std::cell::RefCell; //norun struct TreeNode { value: u32, parent: Option>>, children: Vec >>, } impl TreeNode { fn new(value: u32, parent: Option >>) -> Rc > { Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] })) } } fn main() {}
Обратно към Rc
Side note
Може да си улесните малко живота с type alias:
type TreeNodeRef = Rc<RefCell<TreeNode>>;
struct TreeNode {
value: u32,
parent: Option<TreeNodeRef>,
children: Vec<TreeNodeRef>,
}
impl TreeNode {
fn new(value: u32, parent: Option<TreeNodeRef>) -> TreeNodeRef {
Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] }))
}
}
#![allow(dead_code)] use std::rc::Rc; use std::cell::RefCell; //norun type TreeNodeRef = Rc>; struct TreeNode { value: u32, parent: Option , children: Vec , } impl TreeNode { fn new(value: u32, parent: Option ) -> TreeNodeRef { Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] })) } } fn main() {}
Weak reference
fn make_tree() -> Rc<RefCell<TreeNode>> {
let root = TreeNode::new(0, None);
let v1 = TreeNode::new(1, Some(Rc::clone(&root)));
let v2 = TreeNode::new(2, Some(Rc::clone(&root)));
{
let mut r = root.borrow_mut();
r.children.push(v1);
r.children.push(v2);
}
root
}
fn main() {
let tree = make_tree();
println!("{:?}", tree.borrow().value);
mem::drop(tree);
}
0
#![allow(dead_code)] use std::rc::Rc; use std::cell::RefCell; use std::mem; #[derive(Debug)] struct TreeNode { value: u32, parent: Option>>, children: Vec >>, } impl TreeNode { fn new(value: u32, parent: Option >>) -> Rc > { Rc::new(RefCell::new(TreeNode { value, parent, children: vec![] })) } } fn make_tree() -> Rc > { let root = TreeNode::new(0, None); let v1 = TreeNode::new(1, Some(Rc::clone(&root))); let v2 = TreeNode::new(2, Some(Rc::clone(&root))); { let mut r = root.borrow_mut(); r.children.push(v1); r.children.push(v2); } root } fn main() { let tree = make_tree(); println!("{:?}", tree.borrow().value); mem::drop(tree); }
Weak reference
- нищо не гърми, но родителя държи Rc към децата, а децата държат Rc към родителя
Weak reference
- нищо не гърми, но родителя държи Rc към децата, а децата държат Rc към родителя
- получаваме цикъл от референции -- никога няма да се деалокират
Weak reference
- нищо не гърми, но родителя държи Rc към децата, а децата държат Rc към родителя
- получаваме цикъл от референции -- никога няма да се деалокират
- това води до изтичане на памет
Weak reference
Sidenote
- забележете, че можем да имаме memory leak в safe code
Weak reference
Sidenote
- забележете, че можем да имаме memory leak в safe code
- затова съществува безопасната функция
mem::forget
(https://doc.rust-lang.org/std/mem/fn.forget.html#safety)
Weak reference
Sidenote
- забележете, че можем да имаме memory leak в safe code
- затова съществува безопасната функция
mem::forget
(https://doc.rust-lang.org/std/mem/fn.forget.html#safety) - и затова нямаме гаранция че деструкторите ще се извикат
Weak reference
Sidenote
- забележете, че можем да имаме memory leak в safe code
- затова съществува безопасната функция
mem::forget
(https://doc.rust-lang.org/std/mem/fn.forget.html#safety) - и затова нямаме гаранция че деструкторите ще се извикат
- (повече за деструктори и
mem::drop
скоро)
Weak reference
Да се върнем на проблема с дървото
Weak reference
Да се върнем на проблема с дървото
- искаме родителят да е собственик на децата
Weak reference
Да се върнем на проблема с дървото
- искаме родителят да е собственик на децата
- не искаме детето да е собственик на родителя
Weak reference
Да се върнем на проблема с дървото
- искаме родителят да е собственик на децата
- не искаме детето да е собственик на родителя
- за това се използват силни и слаби референции
Weak reference
use std::mem;
use std::rc::{Rc, Weak};
fn main() {
let rc = Rc::new(10);
let weak = Rc::downgrade(&rc);
println!("{:?}", Weak::upgrade(&weak)); // Option<Rc<T>>
mem::drop(rc);
println!("{:?}", Weak::upgrade(&weak)); // Option<Rc<T>>
}
Some(10) None
use std::mem; use std::rc::{Rc, Weak}; fn main() { let rc = Rc::new(10); let weak = Rc::downgrade(&rc); println!("{:?}", Weak::upgrade(&weak)); // Option> mem::drop(rc); println!("{:?}", Weak::upgrade(&weak)); // Option > }
Reference counting
Weak references
let gosho_source = "Гошо, Гошо, скочи лошо";
let shared_gosho = Rc::new(gosho_source); // shared_gosho { strong = 1, weak = 0 };
let bratcheda = Rc::clone(&shared_gosho); // shared_gosho { strong = 2, weak = 0 };
// или, shared_gosho.clone(), но първото е по-ясно
let slabichko = Rc::downgrade(&shared_gosho); // shared_gosho { strong = 2, weak = 1 };
println!("{:#?}", Weak::upgrade(&slabichko)); // => Some("Гошо, Гошо, скочи лошо")
// shared_gosho { strong = 3, weak = 1 };
// shared_gosho { strong = 2, weak = 1 };
std::mem::drop(bratcheda); // shared_gosho { strong = 1, weak = 1 };
std::mem::drop(shared_gosho); // shared_gosho { strong = 0, weak = 1 }; => DROP!
println!("{:#?}", Weak::upgrade(&slabichko)); // => None
Rc
// Инициализираме споделената стойност
let gosho_source = "Гошо, Гошо, скочи лошо";
let shared_gosho = Rc::new(gosho_source); // Rc<&str>
let bratcheda = Rc::clone(&shared_gosho); // Rc<&str>
let slabichko = Rc::downgrade(&shared_gosho); // Weak<&str>
println!("{:#?}", Weak::upgrade(&slabichko)); // Option<Rc<&str>>
Rc
Ето как изглежда в паметта:
Raw pointers
Ок, нека видим и как изглеждат указателите
let raw_const_ptr: *const u32 = &1_u32 as *const u32;
let raw_mut_ptr: *mut u32 = &mut 1_u32 as *mut u32;
// Coercion
let raw_const_ptr: *const u32 = &1;
let raw_mut_ptr: *mut u32 = &mut 1;
#![allow(dead_code)] #![allow(unused_variables)] fn main() { let raw_const_ptr: *const u32 = &1_u32 as *const u32; let raw_mut_ptr: *mut u32 = &mut 1_u32 as *mut u32; // Coercion let raw_const_ptr: *const u32 = &1; let raw_mut_ptr: *mut u32 = &mut 1; }
Raw pointers
Първо и най-важно правило - указателите са safe, докато не се опитаме да четем или пишем в тях
let raw_const_ptr: *const u32 = &1;
let raw_mut_ptr: *mut u32 = &mut 1;
unsafe {
println!("{}", *raw_const_ptr);
}
// За разлика от референциите, указателите
// могат да са null или dangling pointers
unsafe {
*raw_mut_ptr += 1;
println!("{}", *raw_mut_ptr);
}
1 2
fn main() { let raw_const_ptr: *const u32 = &1; let raw_mut_ptr: *mut u32 = &mut 1; unsafe { println!("{}", *raw_const_ptr); } // За разлика от референциите, указателите // могат да са null или dangling pointers unsafe { *raw_mut_ptr += 1; println!("{}", *raw_mut_ptr); } }
Raw pointers
Нямат Deref и DerefMut. Тук компилатора си има само вградения оператор за това.
(&1 as *const u32).deref();
error[E0599]: no method named `deref` found for raw pointer `*const u32` in the current scope --> src/bin/main_a8c848ea06ad2b67e0c50e5c33a165c61f58c8ce.rs:2:20 | 2 | (&1 as *const u32).deref(); | ^^^^^ method not found in `*const u32` | = note: try using `<*const T>::as_ref()` to get a reference to the type behind the pointer: https://doc.rust-lang.org/std/primitive.pointer.html#method.as_ref = note: using `<*const T>::as_ref()` on a pointer which is unaligned or points to invalid or uninitialized memory is undefined behavior For more information about this error, try `rustc --explain E0599`. error: could not compile `rust` due to previous error
fn main() { (&1 as *const u32).deref(); }
Raw pointers
Някои полезни методи
Raw pointers
Някои полезни методи
is_null(self) -> bool
Raw pointers
Някои полезни методи
is_null(self) -> bool
unsafe fn as_ref<'a>(self) -> Option<&'a T>
Raw pointers
Някои полезни методи
is_null(self) -> bool
unsafe fn as_ref<'a>(self) -> Option<&'a T>
unsafe fn offset(self, count: isize) -> *const T
Raw pointers
Някои полезни методи
is_null(self) -> bool
unsafe fn as_ref<'a>(self) -> Option<&'a T>
unsafe fn offset(self, count: isize) -> *const T
- И други безумия тук: https://doc.rust-lang.org/std/primitive.pointer.html