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!
x
x
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 :
// 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());
}
}
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.
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!