算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
rust-architecture【设计与创意】Use this skill when structuring Rust backend applications — workspace layout, crate organization, feature-based modules, error handling architecture, async runtime selection, and type-driven design. This skill enforces: Cargo workspace for multi-crate projects, Result/Option for error handling, trait-based abstraction, async with tokio, and feature f…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Structure Rust backend applications with workspace layout, type-driven design, error handling architecture, async runtime patterns, and trait-based abstraction.
User request includes: Rust project structure, Rust workspace, Rust crate layout, Rust error handling, Rust async, Rust traits, Rust architecture, Cargo workspace.
Workspace layout, crate structure, error type definitions, trait definitions, async main.
Produce artifact directly. No preamble, no postamble, no explanations.
4096 tokens
| Criterion | tokio | async-std | smol |
|---|---|---|---|
| Ecosystem | Largest (axum, sqlx, tonic) | Medium | Small |
| Runtime model | Multi-threaded work-stealing | Multi-threaded | Single-threaded or custom |
| Feature set | Full (fs, net, process, time) | Full | Minimal |
| Compile time | Moderate | Fast | Fast |
| Production readiness | Mature, widely used | Less used | Niche |
Decision: Web backend → tokio (axum, sqlx, tonic). Simple async → smol. WASM target → smol or wasm-bindgen-futures.
| Criterion | Axum | Actix-web | Rocket | Warp |
|---|---|---|---|---|
| Async runtime | tokio | tokio | tokio | tokio |
| Type safety | High (extractors) | High | Medium | High (filters) |
| Community | Fastest growing | Mature | Declining | Stable |
| Learning curve | Moderate | Moderate | Low | High (combinators) |
| Performance | High | Highest | High | High |
Decision: Modern Rust + type safety → Axum (recommended for new projects). Maximum throughput → Actix-web. Simplicity → Rocket. Functional composable → Warp.
my-api/
Cargo.toml # Workspace definition
Cargo.lock
crates/
api/ # Binary entry point
Cargo.toml
src/
main.rs
config.rs
startup.rs
domain/ # Domain types, traits, errors
Cargo.toml
src/
mod.rs
models/
mod.rs
user.rs
order.rs
errors.rs
repository.rs # Repository traits
services.rs # Domain services
infrastructure/ # Database, HTTP client implementations
Cargo.toml
src/
mod.rs
postgres/
mod.rs
user_repository.rs
http/
mod.rs
handlers/
mod.rs
user_handler.rs
application/ # Use cases, DTOs
Cargo.toml
src/
mod.rs
use_cases/
mod.rs
create_user.rs
dto.rs
// crates/domain/Cargo.toml
[package]
name = "domain"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
uuid = { version = "1", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }
thiserror = "1"
// crates/domain/src/models/user.rs
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: Uuid,
pub email: String,
pub name: String,
pub role: UserRole,
pub active: bool,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UserRole {
Admin,
User,
Moderator,
}
impl User {
pub fn new(email: String, name: String) -> Self {
Self {
id: Uuid::new_v4(),
email: email.to_lowercase(),
name,
role: UserRole::User,
active: true,
created_at: Utc::now(),
}
}
pub fn deactivate(&mut self) {
self.active = false;
}
pub fn is_admin(&self) -> bool {
self.role == UserRole::Admin
}
}
// crates/domain/src/errors.rs
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DomainError {
#[error("resource not found: {0}")]
NotFound(String),
#[error("validation error: {0}")]
Validation(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("unauthorized")]
Unauthorized,
#[error("unexpected error: {0}")]
Unexpected(String),
}
impl From<sqlx::Error> for DomainError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => DomainError::NotFound("Resource".into()),
_ => DomainError::Unexpected(err.to_string()),
}
}
}
// crates/domain/src/repository.rs
use crate::models::user::{User, UserRole};
use crate::errors::DomainError;
#[async_trait::async_trait]
pub trait UserRepository: Send + Sync {
async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, DomainError>;
async fn find_by_email(&self, email: &str) -> Result<Option<User>, DomainError>;
async fn save(&self, user: &User) -> Result<(), DomainError>;
async fn find_all(&self, page: u32, per_page: u32) -> Result<(Vec<User>, u64), DomainError>;
async fn update(&self, user: &User) -> Result<(), DomainError>;
async fn delete(&self, id: Uuid) -> Result<(), DomainError>;
}
// crates/infrastructure/src/postgres/user_repository.rs
use async_trait::async_trait;
use domain::models::user::{User, UserRole};
use domain::errors::DomainError;
use domain::repository::UserRepository;
use sqlx::PgPool;
use uuid::Uuid;
pub struct PgUserRepository {
pool: PgPool,
}
impl PgUserRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
#[async_trait]
impl UserRepository for PgUserRepository {
async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, DomainError> {
let user = sqlx::query_as!(
User,
"SELECT id, email, name, role as 'role: _', active, created_at FROM users WHERE id = $1",
id
)
.fetch_optional(&self.pool)
.await?;
Ok(user)
}
async fn save(&self, user: &User) -> Result<(), DomainError> {
sqlx::query!(
"INSERT INTO users (id, email, name, role, active, created_at) VALUES ($1, $2, $3, $4, $5, $6)",
user.id, user.email, user.name, user.role as UserRole, user.active, user.created_at
)
.execute(&self.pool)
.await?;
Ok(())
}
async fn find_all(&self, page: u32, per_page: u32) -> Result<(Vec<User>, u64), DomainError> {
let offset = ((page - 1) * per_page) as i64;
let limit = per_page as i64;
let users = sqlx::query_as!(
User,
"SELECT id, email, name, role as 'role: _', active, created_at FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2",
limit, offset
)
.fetch_all(&self.pool)
.await?;
let total: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users")
.fetch_one(&self.pool)
.await?;
Ok((users, total.0 as u64))
}
// ... other methods
}
// crates/application/src/use_cases/create_user.rs
use domain::errors::DomainError;
use domain::models::user::User;
use domain::repository::UserRepository;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct CreateUserInput {
pub email: String,
pub name: String,
}
pub struct CreateUserUseCase<R: UserRepository> {
repository: R,
}
impl<R: UserRepository> CreateUserUseCase<R> {
pub fn new(repository: R) -> Self {
Self { repository }
}
pub async fn execute(&self, input: CreateUserInput) -> Result<User, DomainError> {
if !input.email.contains('@') {
return Err(DomainError::Validation("Invalid email".into()));
}
if input.name.len() < 2 {
return Err(DomainError::Validation("Name too short".into()));
}
if self.repository.find_by_email(&input.email).await?.is_some() {
return Err(DomainError::Conflict("Email already exists".into()));
}
let user = User::new(input.email, input.name);
self.repository.save(&user).await?;
Ok(user)
}
}
// crates/api/src/main.rs
use axum::{Router, Server};
use std::net::SocketAddr;
mod config;
mod startup;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
let config = config::load()?;
let app = startup::create_app(config.clone()).await?;
let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
tracing::info!("Server starting on {}", addr);
Server::bind(&addr)
.serve(app.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}
async fn shutdown_signal() {
tokio::signal::ctrl_c().await.expect("Failed to listen for shutdown");
tracing::info!("Shutting down...");
}
// crates/api/src/startup.rs
use axum::{routing::post, Router};
use infrastructure::postgres::user_repository::PgUserRepository;
use sqlx::PgPool;
use std::sync::Arc;
pub async fn create_app(config: Config) -> Result<Router, sqlx::Error> {
let pool = PgPool::connect(&config.database_url).await?;
let user_repo = Arc::new(PgUserRepository::new(pool));
let app = Router::new()
.route("/api/v1/users", post(handlers::create_user))
.with_state(user_repo);
Ok(app)
}
// crates/api/src/config.rs
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
pub port: u16,
pub database_url: String,
pub log_level: String,
pub jwt_secret: String,
}
impl Config {
pub fn load() -> Result<Self, config::ConfigError> {
let c = config::Config::builder()
.add_source(config::Environment::default())
.build()?;
c.try_deserialize()
}
}
thiserror for library errors (domain, application)anyhow::Error or eyre::Report for application-level (main.rs, handlers)unwrap() or expect() in production code — use ? propagationIntoResponse for DomainError to return consistent HTTP responses--release for production builds (enables all optimizations)lto = "fat" in Cargo.toml for binary cratedeadpool or sqlx::PgPool (built-in)| Anti-Pattern | Why | Fix |
|---|---|---|
unwrap()/expect() in library code | Panics on error | ? operator with proper error types |
| Single crate monolith | Long compile times, poor modularity | Cargo workspace |
Box<dyn Error> for domain errors | Loses type info | thiserror/derive_more |
Async trait without async-trait | Nightly-only | Use #[async_trait] |
Rc<RefCell<T>> in async code | Not Send, can't cross await | Arc<Mutex<T>> or Arc<RwLock<T>> |
| No feature flags for optional deps | Unnecessary compile time | cfg(feature = "...") for platform-specific code |
secrecy crate for sensitive strings (password, tokens) — Zeroize on dropsqlx uses prepared statements by default — never raw string formattingjsonwebtoken crate with validation of alg, exp, issvalidator crate derive macrostower-http cors middleware with explicit originstower middleware with governor cratetower-http SetResponseHeaderLayer#[cfg(test)]
mod tests {
use super::*;
use domain::models::user::User;
struct MockUserRepository;
#[async_trait]
impl UserRepository for MockUserRepository {
async fn find_by_id(&self, _id: Uuid) -> Result<Option<User>, DomainError> {
Ok(None)
}
async fn find_by_email(&self, email: &str) -> Result<Option<User>, DomainError> {
if email == "existing@test.com" {
Ok(Some(User::new("existing@test.com".into(), "Existing".into())))
} else {
Ok(None)
}
}
async fn save(&self, _user: &User) -> Result<(), DomainError> {
Ok(())
}
// ... other methods
}
#[tokio::test]
async fn test_create_user_duplicate_email() {
let use_case = CreateUserUseCase::new(MockUserRepository);
let input = CreateUserInput {
email: "existing@test.com".into(),
name: "Test".into(),
};
let result = use_case.execute(input).await;
assert!(matches!(result, Err(DomainError::Conflict(_))));
}
}
Use #[tokio::test] for async tests. Use mockall or manual mock traits for repository mocking. Use sqlx::test for DB integration tests. Use cargo test with --test-threads=1 for DB tests.
thiserror for error types — #[derive(Error)] with #[error("...")] format strings.#[async_trait] for async and Send + Sync bound for thread safety.sqlx::query! macro for compile-time SQL verification — never raw string queries.cfg(feature = "postgres").tokio::main for async entry point with multi-threaded runtime.config or envy crate.Hand off to backend/rust/patterns/SKILL.md for Rust-specific patterns or backend/universal/api-response/SKILL.md for API response formatting.
function createModule<T>(config: ModuleConfig): T { const dependencies = initializeDependencies(config); const module = new Module(dependencies); module.hooks.onInit(); return module as T; }
class ConfigBuilder { private config: AppConfig = new AppConfig(); withDatabase(url: string): ConfigBuilder { ... } withCache(ttl: number): ConfigBuilder { ... } withLogging(level: string): ConfigBuilder { ... } build(): AppConfig { return this.config; } }
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% | Critical | Rollback or fix |
| p95 latency | > 500ms | Warning | Profile and optimize |
| Uptime | < 99.9% | Critical | Investigate infrastructure |
| Memory usage | > 80% | Warning | Check for leaks |
| CPU usage | > 80% | Warning | Scale up or optimize |