Rust Odyssey

Lessons
Login Join

x

Chapter 2

Boum

1. Borrow

Comprendre les String en Rust

En Rust, les chaînes de caractères (String) sont un concept clé, mais elles peuvent sembler un peu déroutantes au début en raison de la gestion stricte de la mémoire du langage. Contrairement à d'autres langages où les chaînes sont plus abstraites, Rust fournit deux types principaux pour représenter les chaînes de caractères : String et &str (ou string slice). Dans cet article, nous allons explorer ces deux types, comprendre leurs différences et voir comment les utiliser efficacement.

Voici un exemple de création d'une String :

Salut ceci est pour voir
// main.rs

#![allow(dead_code, unused_variables)]

use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex, RwLock};
use std::fmt::Debug;
use std::time::Duration;

mod domain;
mod infrastructure;
mod application;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pool = infrastructure::db::create_pg_pool().await?;
    let ctx = infrastructure::context::PgContext::new(pool);
    let service = application::ConnectDeviceService::new(&ctx, &ctx);

    service.execute(42, "device-123".to_string()).await?;

    Ok(())
}

// --------- domain.rs ---------
pub mod domain {
    use std::collections::HashSet;

    pub type UserId = u64;
    pub type DeviceId = String;

    #[derive(Debug, Clone)]
    pub struct User {
        pub id: UserId,
        pub email: String,
        pub devices: HashSet<DeviceId>,
    }

    impl User {
        pub fn can_connect_new_device(&self) -> bool {
            self.devices.len() < 3
        }

        pub fn connect_device(&mut self, device: DeviceId) -> Result<(), &'static str> {
            if self.can_connect_new_device() {
                self.devices.insert(device);
                Ok(())
            } else {
                Err("Too many devices")
            }
        }
    }

    pub trait UserRepo {
        fn find(&self, id: UserId) -> Option<User>;
        fn save(&self, user: &User);
    }

    pub trait UserSessionRepo {
        fn get_active_devices(&self, user_id: UserId) -> HashSet<DeviceId>;
        fn save_device(&self, user_id: UserId, device: DeviceId);
    }

    pub trait Notifier {
        fn send_login_email(&self, user: &User, device: &DeviceId);
    }
}

// --------- application.rs ---------
pub mod application {
    use super::domain::*;
    use async_trait::async_trait;

    pub struct ConnectDeviceService<'a> {
        user_repo: &'a dyn UserRepo,
        session_repo: &'a dyn UserSessionRepo,
        notifier: &'a dyn Notifier,
    }

    impl<'a> ConnectDeviceService<'a> {
        pub fn new(
            user_repo: &'a dyn UserRepo,
            session_repo: &'a dyn UserSessionRepo,
            notifier: &'a dyn Notifier,
        ) -> Self {
            Self {
                user_repo,
                session_repo,
                notifier,
            }
        }

        pub async fn execute(&self, user_id: u64, device: String) -> Result<(), &'static str> {
            let mut user = self
                .user_repo
                .find(user_id)
                .ok_or("User not found")?;

            user.devices = self.session_repo.get_active_devices(user_id);

            user.connect_device(device.clone())?;

            self.session_repo.save_device(user_id, device.clone());

            self.notifier.send_login_email(&user, &device);

            Ok(())
        }
    }
}

// --------- infrastructure.rs ---------
pub mod infrastructure {
    use super::domain::*;
    use std::collections::HashSet;
    use std::sync::{Arc, Mutex};

    pub mod db {
        use sqlx::PgPool;
        use std::error::Error;

        pub async fn create_pg_pool() -> Result<PgPool, Box<dyn Error>> {
            let pool = PgPool::connect("postgres://user:pass@localhost/db").await?;
            Ok(pool)
        }
    }

    pub mod context {
        use super::*;
        use sqlx::PgPool;

        pub struct PgContext {
            pub user_repo: PgUserRepo,
            pub session_repo: PgUserSessionRepo,
        }

        impl PgContext {
            pub fn new(pool: PgPool) -> Self {
                Self {
                    user_repo: PgUserRepo { pool: pool.clone() },
                    session_repo: PgUserSessionRepo { pool },
                }
            }
        }

        pub struct PgUserRepo {
            pub pool: PgPool,
        }

        impl UserRepo for PgUserRepo {
            fn find(&self, id: u64) -> Option<User> {
                // mock
                Some(User {
                    id,
                    email: "user@example.com".to_string(),
                    devices: HashSet::new(),
                })
            }

            fn save(&self, user: &User) {
                println!("Saving user {:?}", user);
            }
        }

        pub struct PgUserSessionRepo {
            pub pool: PgPool,
        }

        impl UserSessionRepo for PgUserSessionRepo {
            fn get_active_devices(&self, _user_id: u64) -> HashSet<String> {
                HashSet::new()
            }

            fn save_device(&self, user_id: u64, device: String) {
                println!("Saving device {} for user {}", device, user_id);
            }
        }

        pub struct SmtpNotifier;

        impl Notifier for SmtpNotifier {
            fn send_login_email(&self, user: &User, device: &String) {
                println!(
                    "Sending email to {} about device {}",
                    user.email, device
                );
            }
        }
    }
}

// --------- tests.rs ---------
#[cfg(test)]
mod tests {
    use super::domain::*;
    use super::application::*;

    struct FakeUserRepo;
    struct FakeSessionRepo;
    struct FakeNotifier;

    impl UserRepo for FakeUserRepo {
        fn find(&self, _id: u64) -> Option<User> {
            Some(User {
                id: 1,
                email: "test@example.com".to_string(),
                devices: HashSet::new(),
            })
        }
        fn save(&self, _user: &User) {}
    }

    impl UserSessionRepo for FakeSessionRepo {
        fn get_active_devices(&self, _user_id: u64) -> HashSet<String> {
            HashSet::new()
        }
        fn save_device(&self, _user_id: u64, _device: String) {}
    }

    impl Notifier for FakeNotifier {
        fn send_login_email(&self, _user: &User, _device: &String) {}
    }

    #[tokio::test]
    async fn test_connect_device() {
        let service = ConnectDeviceService::new(
            &FakeUserRepo,
            &FakeSessionRepo,
            &FakeNotifier,
        );
        assert!(service.execute(1, "device-xyz".to_string()).await.is_ok());
    }
}

Bonsange

En Rust, les chaînes de caractères (String) sont un concept clé, mais elles peuvent sembler un peu déroutantes au début en raison de la gestion stricte de la mémoire du langage. Contrairement à d'autres langages où les chaînes sont plus abstraites, Rust fournit deux types principaux pour représenter les chaînes de caractères : String et &str (ou string slice). Dans cet article, nous allons explorer ces deux types, comprendre leurs différences et voir comment les utiliser efficacement.

Want to learn more? 👋

That's the end of the free part 😔

To get access to 93 hours of video, a members-only Discord server and future updates, join us for only $95!

Login Join