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!