Memória kezelés Rust nyelvvel
Szecsődi Imre
Mi az a Rust?
- Systems Programming language
- Szintaxisban hasonlít a C++-ra
- Gyors mint a C++
- Mozilla Research projekt
- Legszeretettebb nyelv Stack Overflown 2016 óta
Mi íródott Rust-ban?
- Rust compiler
Mi íródott Rust-ban?
- Servo layout engine
Memória kezelési módszerek
- Kézi foglalás és felszabadítás - C/C++
- Szemétgyűjtés - Java, Python, stb.
- Tulajdonos alapú kezelés - Rust
Kézi memória kezelés C/C++
- Azok az értékek aminek futás időben ismert a mérete azok a stacken fognak létrejönni, ide tartoznak a primitív változók
- Amit dinamikusan hozunk létre, azok a heapen fognak létrejönni, amit majd fel kell onnan szabadítani
- Attól függően, hogy smart vagy raw pointerről beszélünk, az elsőnél a program gondoskodik majd a felszabadításról, utóbbi esetben nekünk kell azt kézzel
Kézi memória kezelés C/C++
int main() {
int i = 32;
char (*pchar)[10] = new char[dim][10];
delete [] pchar;
}
Szemétgyűjtés - Java
- Korábbi órákon esett róla szó
- A program futás időben nyilvántartja az objektumokat, és mikor véget ér az objektum élettartama, akkor meg lesz jelölve törlésre, és a szemétgyűjtéskor fel fog szabadulni
Tulajdonos alapú - Rust
- Alapötlet: csak azokat az objektumokat tudjuk garantálni, hogy felszabadulnak amik stacken helyezkednek el
- Minden objektumhoz ami létrejön heapen, tartozik egy minimális objektum a stacken is
- Ezért objektumoknak csak egy tulajdonosuk lehet egy időben
Fordítóval való harc
- Mikor először írunk Rust kódot, fel fog tűnni, hogy a fordító nagyon sok mindenre hibát fog jelezni, a borrow checker miatt
- A fordító elvárja, hogy a program memória biztosan legyen megírva, így sok hibát már fordítás időben kiküszöbölhetünk:
- Buffer túlcsordulás
- Felszabadítás utáni használat
- Nem inicializált változók használata
- Null pointer használata
- Kétszeri felszabadítás, stb.
Fordítóval való harc
- Ezeket a dolgokat statikus elemzéssel a fordító még időnap előtt megfogja, és egy hasznos hibaüzenettel közli velünk a problémát, és javaslatot is ad javításra
- A Rust az biztonságos nyelv, még betartjuk ezeket a szabályokat, de van lehetőség \(unsafe\) blokk írásra, ahol nem lesz ellenőrizve, és ott előjöhetnek problémát futás időben
- Ez főleg külső C könyvtáraknál fordul elő
- A Rust biztonságossága csak memóriára vonatkozik, nem véd erőforrás szivárgás ellen, pl. nyitva maradt socket handler
Hol jönnek létre a dolgok?
- Aminek ismert fordítás időben a mérete, azok mind a stacken jönnek létre, ezzel garantálva, hogy mikor kilépünk a blokkból, stack kiürítésekor rögtön felszabadul minden
- Dinamikus métertű objektumok a heapen jönnek létre, ide tartozik a String struktura, és a konténerek
- A tulajdonos alapú memória kezelés arra hivatott, hogy karbantartsa a heapet, és onnan is törlődjön az objektum, mikor a blokk végére értünk
- Ez nagyon hasonlít a RAII-ra a C++ nyelvben
Tesztkörnyezet
- https://play.rust-lang.org/
- Link
Mutability
fn main()
{
let name = "Thomas";
name = "Jane";
println!("Name is: {}", name);
}
error[E0384]: cannot assign twice to immutable variable
- Mi a hiba a kódban?
Mutability
- Rustban változót a \(let\) kulcsszóval definiálunk, és ez Immutable alapból, azaz nem változtatható az értéke
- Ahhoz, hogy ezen változtassunk ki kell rakni a \(mut\) kulcsszavat
- Javítsuk ki az előző példát
Mutability
fn main()
{
let mut name = "Thomas";
name = "Jane";
println!("Name is: {}", name);
}
Referenciák
fn main()
{
let mut name = "Thomas";
name = "Jane";
let name_ref = &name;
println!("Name is: {}", name);
}
- Mint C/C++ készíthetünk referenciát a változó címére
- Itt a name_ref immutable, mivel a referencia független a változótól
Referenciák
fn main()
{
let name = "Thomas";
let name_ref = &mut name;
}
- Mutable változóra lehet Immutable ref, de fordítva nem
error[E0596]: cannot borrow immutable local variable name as mutable
Struktúrák
#[derive(Debug)]
struct Player
{
name: String,
age: u8,
description: String
}
- A Rust nyelv nem feltétlen követi az objektum orientált alapelveket
- Inkább hasonlít a C struktúrákra amihez definiálhatunk műveleteket
Struktúrák
impl Drop for Player{
// Destruktor függvény megadása
fn drop(&mut self) {
println!("Destrukor lefutott {:?}", self);
}
}
impl Player{
fn get_older(&mut self) {
age+=1;
}
}
Struktúrák
fn main()
{
let mut our_player = Player
{
name: "Jones".to_string(), //converts &str (static string) to a String (heap memory)
age: 25,
description: "Just a happy guy.".to_string()
};
}
Move
- Move-nak nevezzük azt mikor átadjuk a tulajdonosi jogot
- Miután átadtuk azután már az eredeti referencia megszűnik
- Analógia: mikor eladjuk az autónkat, azután már az új tulajdonos fogja használni, és nekünk nem lesz hozzáférésünk
String példa
- Mikor megnézzük ezt a kódot azt várjuk, hogy létrehozunk heapen egy \(s1\) stringet, majd \(s2\)-re állítjuk a referenciát
- Ez nem egészen így történik
let s1 = String::from("hello");
let s2 = s1;
String példa
let s1 = String::from("hello");
let s2 = s1;
String példa
let s1 = String::from("hello");
let s2 = s1;
String példa
let s1 = String::from("hello");
let s2 = s1;
String példa
let s1 = String::from("hello");
let s2 = s1;
String példa
let s1 = String::from("hello");
let s2 = s1;
Függvény példa
- A függvény átveszi a tulajdonos jogot attól az objektumtól ami bemegy
- Majd visszatérési értékként visszaadja a tulajdonosi jogot a fogadó félnek
fn mover(moved: Player) -> Player
{
println!("I am {}, I've been moved into mover", moved.name);
moved
}
Függvény példa
- Hol a hiba?
fn main()
{
let mut our_player = Player
{
name: "Jones".to_string(),
age: 25,
description: "Just a happy guy.".to_string()
};
mover(our_player);
println!("My name is {}, and I am being used after a move", our_player.name);
}
error[E0382]: use of moved value: our_player.name
Függvény példa - Fix - Immautable Borrow
fn immutable_borrow(borrowed: &Player)
{
println!("I am {}, I've been immutably borrowed", borrowed.name);
}
fn main()
{
let mut our_player = Player
{
name: "Jones".to_string(),
age: 25,
description: "Just a happy guy.".to_string()
};
immutable_borrow(&our_player);
println!("My name is {}, and I am being used after an immutable borrow", our_player.name);
}
Függvény példa 2
- Mit a hiba a kódban?
- Semmi!
- Mivel az alap típusok implementálják a Copy Trait-et, ezért mikor Move történne lemásolódik az érték
fn main()
{
let age: u8 = 55;
mover(age);
println!("The age is: {}", age);
}
fn mover(age: u8) -> u8
{
println!("Age {} has been moved into mover!", age);
age
}
Primitív példa
- Létrehozunk egy \(x\) változót a stacken 5 értékkel
- Utána létrehozunk még egy \(y\) változót a stacken, az \(x\) értékével, ami 5, és kaptunk két különböző változót, két darab 5-ös van a stacken
let x = 5;
let y = x;
Move - Rebind
- Mi történik ilyenkor?
- Van egy Immutable változónk, és azt Move segítségével átadjuk a függvénynek
- Mivel nem mi vagyunk már a tulajdonos, az lehet már Muable is az új helyen, Rebind történt
#[derive(Debug, Clone)]
struct User {
a: String,
b: usize,
}
fn mover_function_with_return(mut user: User) -> User {
user.b = 456;
user
}
fn main()
{
let b = User{ a:String::from("Test2"), b: 234566 };
mover_function_with_return(b);
}
Borrowing - Immutable
- Az előző példában már láthattunk példát Immutable borrow-ra
- Átadunk úgy egy változót, hogy a tulajdonos jogot nem adtuk át, ezért a függvény lefutása után is elérhető lesz az eredeti változó
- Annyi Immutable Borrow-t készíthetünk, amennyit csak szeretnénk, de csak addig, még a scope nem tartalmaz Mutable Borrow-t
fn immutable_borrow(borrowed: &Player)
{
println!("I am {}, I've been immutably borrowed", borrowed.name);
}
immutable_borrow(&our_player);
Borrowing - Mutable
- Az Immutable borrowing csak olvasható
- Ha változtatni akarunk akkor Mutable borrow-ra van szükségünk
- Nézzük meg a szignatúrákat
fn main()
{
immutable_borrow(&our_player);
change_name(&mut our_player);
}
fn immutable_borrow(borrowed: &Player)
{
println!("I am {}, I've been immutably borrowed", borrowed.name);
}
fn change_name(borrowed: &mut Player)
{
borrowed.name = "My New Name".to_string();
}
Borrowing - Mutable
- A biztonságos Rust komoly szabályokat követel meg mikor Mutable Borrow történik
- Egyszerre csak EGY darab Mutable referencia lehet egy változóra, és nem lehet egyetlen Immutable sem
- Ezzel a Rust megakadályozza azt, hogy verseny helyzet alakuljon ki a változóra
- Ez persze kikapcsolható, de akkor \(unsafe\) blokkot kell használni
Borrowing - Mutable
- Mi a hiba a kódban?
- Rossz compiler opció van beállítva
fn main()
{
let my_immutable_return = immutable_borrow(&our_player);
change_name(&mut our_player);
println!("My name is {}, and I am being used after an mutable borrow", our_player.name);
}
fn immutable_borrow(borrowed: &Player) -> &Player
{
println!("I am {}, I've been immutably borrowed", borrowed.name);
borrowed
}
fn change_name(borrowed: &mut Player)
{
borrowed.name = "My New Name".to_string();
}
Borrowing - Mutable
- Ez hibát ad, mivel a borrowing az a blokk végéig tart, és ez miatt lesz egy Immutable és Mutable borrow is
fn main()
{
let my_immutable_return = immutable_borrow(&our_player);
change_name(&mut our_player);
println!("My name is {}, and I am being used after an mutable borrow", our_player.name);
}
fn immutable_borrow(borrowed: &Player) -> &Player
{
println!("I am {}, I've been immutably borrowed", borrowed.name);
borrowed
}
fn change_name(borrowed: &mut Player)
{
borrowed.name = "My New Name".to_string();
}
Borrowing - Mutable
- Ha egy új blokkba rakjuk bele az Immutable Borrow-t akkor az előbb meg fog szűnni, mint mielőtt átadjuk Mutable Borrow-t
fn main()
{
{
let my_immutable_return = immutable_borrow(&our_player);
}
change_name(&mut our_player);
println!("My name is {}, and I am being used after an mutable borrow", our_player.name);
}
Miért jó a kód mégis 2018-ban?
Rust memória műveletek- röviden
fn main() {
// Konstans változó
let a : i32 = 123;
// Nem konstans változó
let mut b : i32 = 987;
// Objektum létrehozás
let c = User{ a:String::from("Test2"), b: 234566 };
// Move
let d : User = c;
// Copy, ha implementálja a Copy-t a típus
let d1 : i32 = a
// Clone, ha implementálja a Clone-t a típus
let e : User = c.clone()
// Immutable Borrow
let f : i32 = &c.b;
// Mutable Borrow
let g : i32 = &mut c.b;
}
Összefoglalás
- A Rust nyelv határozottan más gondolkodás módot követel meg a fejlesztőktől
- A nyelv biztonságossága miatt, nagyon jól használható konkurens programok írására
- Sebességre felveszi a versenyt a legjobb C++ kódokkal
- Határozottan jobban kezeli a hibát mint a C/C++
- Mivel azon ritka nyelvek közé tartozik ami nem rendelkezik GC-vel, a WebAssembly egyik kiemelt nyelve, amivel érdemes arra fejleszteni
Referenciák
- Hivatalos Rust könyv - https://doc.rust-lang.org/book
-
Rust Mutability, Moving and Borrowing - The Straight Dope - Cikk a Rust fórumon - https://users.rust-lang.org/t/rust-mutability-moving-and-borrowing-the-straight-dope/22166
Köszönöm a figyelmet
Rusty
By Imre Secodi
Rusty
- 123