A napokban átírtam a mostmár TenantsModule-t (korábban OrganizationalUntisModule, mert refaktoroltam is kicsit :)) így jelen állapotában így néz ki:

1pub struct TenantsModule {
2    pub pool_manager: Arc<dyn PgPoolManagerTrait>,
3    pub config: Arc<AppConfig>,
4    pub repo_factory: Box<dyn Fn() -> Box<dyn TenantsRepository + Send + Sync> + Send + Sync>,
5    pub migrator_factory: Box<dyn Fn() -> Box<dyn DatabaseMigrator + Send + Sync> + Send + Sync>,
6}

De mik ezek a dobozok? Link to heading

Ahhoz, hogy ezt szemléltetni tudjam, egy rövid kitérőt kell tennem a memóriák világába. Ebben a témában két fontos szintet különböztetünk meg a rendelkezésre álló memóriából:

A stack (azt hiszem magyarul verem) Link to heading

Ezt a memóriaterületet a program indulásakor az operációs rendszer (konkrétabban a kernel) foglalja le nekünk. Hogy ennek a területnek mekkora legyen a mérete, azt a program fordításakor a fordító határozza meg. Ezt csak akkor tudja megtenni, ha a stackre csak olyan dolgokat pakolunk, aminek fordításidőben már ismerjük a méretét ( lásd: Sized trait). Azokban az esetekben, amikor fordításidőben nem tudjuk megmondani, hogy mekkora helyre lesz szükségünk egy adott dolog tárolásához vagy olyan konstrukcióban dolgozunk, ahol nem tudjuk kizárni, hogy a méret változik, a heap memóriát kell használnunk.

A heap (azt hiszem magyarul halom) Link to heading

Futásidőben dinamikusan változó értékek esetén, amelyeket nem rakhatunk a stackre, megkérhetjük a kernelt, hogy foglaljon nekünk további helyet a heapen, ahol dolgozhatunk az adatainkkal. Ez jól hangzik, de van egy kis ára: idő előtt vissza kell adnunk a vezérlést a kernelnek, aki elvégzi a memóriaterület lefoglalását és ha már úgy alakult, hogy megkapta a vezérlést lehet, hogy még megcsinál ezt-azt. <kiskitérő> Többek között ezért tud több program látszólag egyszerre futni a számítógépeden akkor is, ha csak egy processzor van benne egy maggal, mert ha egy program visszaadja a vezérlést a kernelnek, akkor az újra allokálja az erőforáásokat: Például hagy futni egy másik programot.</kiskitérő>.

Remélem még nem unod a kitérőket, mert itt van még egy: Kód kompozíció Link to heading

Minden sor kód, amit megírsz egy újabb sor kód, amit ha hosszú távon használni szeretnél, akkor karban kell tartanod, ezért fejlesztőként törekszel arra, hogy a megírt eljárásaid minél univerzálisabban használhatóak legyenek különböző adattípusokra és/vagy adatszerkezetekre, így kevesebb kódot kell írnod és karbantartanod. Ennek egyik eszköze a generikusok, a másik pedig a traitek. A bejegyzés témájának gyakorlati szempontjából ez utóbbiak az érdekesebbek, úgyhogy a generikusokról majd talán egyszer egy másik bejegyzésben lesz szó.

Traitek Link to heading

A trait meghatároz egy funkcionalitást, amivel rendelkezhet egy típus és amin osztozhat más típusokkal. Azt hiszem valami ilyesmit kellene írni a papírra, ha ezzel kapcsolatos tesztet töltesz ki, de mit jelent ez?

Adott három jármű típus. A feladat, hogy írassuk ki a gyártmányukat a pogramunkkal

Hogy néz ez ki traitek nélkül?

 1struct Repulo {
 2    pub gyartmany: String,
 3    pub hivojel: String,
 4    // ...
 5}
 6struct Auto {
 7    pub gyartmany: String,
 8    pub bologatos_kutya_van_benne: bool
 9    // ...
10}
11
12struct Bicikli {
13    pub gyartmany: String,
14    pub nyereg_tipus: String,
15    // ...
16}
17
18// Amit itt észre kell venni, hogy van egy eljárásom, amit háromszor kellett megírnom annak ellenére, hogy ugyanazt 
19// csinálja. Ebben a példában ez nem akkora probléma, mert csak egy println makrót hív a függvény, de képzeld el ugyanezt
20// 50-60 soros függvényekkel és sokkal több féle járművel.
21fn print_repulo_gyartmany(repulo: &Repulo) {
22    println!("Gyártmány: {}", &repulo.gyartmany);
23}
24
25fn print_auto_gyartmany(auto: &Auto) {
26    println!("Gyártmány: {}", &auto.gyartmany);
27}
28
29fn print_bicikli_gyartmany(bicikli: &Bicikli) {
30    println!("Gyártmány: {}", &bicikli.gyartmany)
31}
32
33fn main() {
34    let repulo = Repulo { gyartmany: String::from("Cessna"), hivojel: String::from("KD123456") };
35    let auto = Auto { gyartmany: String::from("Audi"), bologatos_kutya_van_benne: true };
36    let bicikli = Bicikli { gyartmany: String::from("Merida"), nyereg_tipus: String::from("ASD Royal+ Hybrid") };
37    print_repulo_gyartmany(&repulo);
38    print_auto_gyartmany(&auto);
39    print_bicikli_gyartmany(&bicikli);
40}

Hogy néz ez ki traitekkel?

 1trait VanGyartmany {
 2    fn get_gyartmany(&self) -> &String;
 3}
 4struct Repulo {
 5    pub gyartmany: String,
 6    pub hivojel: String,
 7    // ...
 8}
 9struct Auto {
10    pub gyartmany: String,
11    pub bologatos_kutya_van_benne: bool
12    // ...
13}
14
15struct Bicikli {
16    pub gyartmany: String,
17    pub nyereg_tipus: String,
18    // ...
19}
20
21impl VanGyartmany for Repulo {
22    fn get_gyartmany(&self) -> &String {
23        &self.gyartmany
24    }
25}
26impl VanGyartmany for Auto {
27    fn get_gyartmany(&self) -> &String {
28        &self.gyartmany
29    }
30}
31impl VanGyartmany for Bicikli {
32    fn get_gyartmany(&self) -> &String {
33        &self.gyartmany
34    }
35}
36
37// Itt látszólag egy változó méretű paramétert adok át ennek a függvénynek, amire nincs lehetőség, de ez azért van,
38// mert ez a függvény valójában egy jól álcázott generikus függvény
39fn print_gyartmany(jarmu: impl VanGyartmany) {
40    println!("Gyártmány: {}", jarmu.get_gyartmany());
41}
42
43// ami kibontva így néz ki:
44fn print_gyartmany_kibontva<T: VanGyartmany>(jarmu: T) {
45    println!("Gyártmány: {}", jarmu.get_gyartmany());
46}
47
48// Ezt a függvényt meg lehetne úgy is írni, hogy 
49fn print_gyartmany_box(jarmu: Box<dyn VanGyartmany>) {
50    println!("Gyártmány: {}", jarmu.get_gyartmany());
51}
52// ebben az esetben a hívó felet arra kényszerítaném, hogy kérje meg a kernelt, hogy adjon neki helyet a heap-en,
53// pakolja át oda az adatokat és ennek a függvénynek azt a pointert adja át, ami a heap-en lévő adatokra mutat.
54// Ezt a költséges műveletet itt még meg tudom sprólni az impl Trait-el. 
55
56fn main() {
57    let repulo = Repulo { gyartmany: String::from("Cessna"), hivojel: String::from("KD123456") };
58    let auto = Auto { gyartmany: String::from("Audi"), bologatos_kutya_van_benne: true };
59    let bicikli = Bicikli { gyartmany: String::from("Merida"), nyereg_tipus: String::from("ASD Royal+ Hybrid") };
60    print_gyartmany(repulo);
61    print_gyartmany(auto);
62    print_gyartmany(bicikli);
63}

De akkor végülis mik ezek a dobozok? Link to heading

Alakítsuk át az előző példát úgy, hogy a print függvényünk ne egy példányt tudjon kezelni, hanem példányok egy vektorát

 1
 2// A függvényt csak olyan paraméterrel tudjuk meghívni, aminek fordításidőben ismert a mérete.
 3// Itt egy pointereket tartalamazó vektor pointerét adjuk át. A pointer mérete ismert fordításidőben
 4fn print_gyartmany(jarmuvek: &Vec<Box<dyn VanGyartmany>>) {
 5    for jarmu in jarmuvek {
 6        println!("Gyártmány: {}", jarmu.get_gyartmany());
 7    }
 8}
 9
10fn main() {
11    let repulo = Repulo { gyartmany: String::from("Cessna"), hivojel: String::from("KD123456") };
12    let auto = Auto { gyartmany: String::from("Audi"), bologatos_kutya_van_benne: true };
13    let bicikli = Bicikli { gyartmany: String::from("Merida"), nyereg_tipus: String::from("ASD Royal+ Hybrid") };
14
15    // ez nem fog menni, mert a változók típusa és mérete nem egyezik
16    // let jarmuvek = vec![repulo, auto, bickili];
17
18    //A megoldás:
19
20    // Kérjünk helyet a kerneltől a heap-en, másoljuk át oda a példányainkat, a hozzájuk tartozó pointert pedig tároljuk
21    // el a megfelelő változóban
22
23    let repulo = Box::new(repulo);
24    let auto = Box: new(auto);
25    let bicikli = Box::new(bicikli);
26
27    // Kérjünk helyet a heap-en a vektorunk-nak, a hozzá tartozó pointert pedig tároljuk el a jarmuvek változóban
28    let jarmuvek: Vec<Box<dyn VanGyartmany>> = vec![repulo, auto, bicikli];
29
30    print_gyartmany(&jarmuvek);
31}

Tehát összefoglalva a dobozok (Box) egy megoldás a sok közül, hogy helyet kérjünk a heap-en, ahol futásidőben dinamikusan változó adatokkal tudunk dolgozni, a rájuk mutató pointert pedig úgy tudjuk használni, hogy annak már fordításidőben ismerjük a méretét.

Amikor terveztem ezt a bejegyzés sokkal rövidebbnek gondoltam és azzal a reménnyel álltam neki, hogy nagyságrendekkel kedvesebb időt fog igénybevenni a megírása. Ha találsz benne hibát, akkor írj egy e-mailt!