使用 Rust 和 DDD 构建 API 服务器

Introduction 介绍

I tried implementing an API server using Rust and the Axum framework.
我尝试使用 Rust 和 Axum 框架实现 API 服务器。

Target Audience 本文受众

  • Those who want to implement an API server with Rust.
    那些想要用 Rust 实现 API 服务器的人。
  • Those who want to implement DDD with Rust.
    那些想要用 Rust 实现 DDD (
    Domain-driven design领域驱动设计)的人。

Not Covered 本文不包括

  • Basic Rust syntax. 基本 Rust 语法。
  • Basic concepts of DDD. DDD 的基本概念。
  • How to use specific crates.
    如何使用特定的板条箱。

Dependency Direction 依赖方向

ddd

The architecture we are creating has the dependency relationship as shown above. Keeping this dependency relationship in mind while reading the article will enhance your understanding. It’s important to note that the infrastructure layer does not depend on the application layer.
我们正在创建的架构具有如上所示的依赖关系。阅读本文时牢记这种依赖关系将增强您的理解。需要注意的是,基础设施层不依赖于应用程序层。

Implementation Begins 实施开始

Specifying Requirements 明确要求

This time, we decided to create a system for universities to manage clubs.
这次,我们决定创建一个大学管理俱乐部的系统。

  • Ability to add members. 能够添加成员。
  • Fourth-year students cannot be added.
    无法添加四年级学生。
  • Ability to remove members.
    能够删除成员。
  • Owners cannot be removed.
    无法删除所有者。
  • Fourth-year students graduate.
    四年级学生毕业。
  • Clubs require a minimum of 3 members to operate.
    俱乐部需要至少 3 名会员才能运营。
  • Clubs have a maximum capacity.
    俱乐部有最大容纳人数。
  • Clubs require a representative.
    俱乐部需要一名代表。
  • Individuals aged 20 and above can join social gatherings.
    20岁及以上的个人可以参加社交聚会。
  • Only third-year students can become club representatives.
    只有三年级学生才能成为俱乐部代表。

Domain Layer 领域层

Let’s start by creating the domain layer. The Circle aggregate consists of two entities: Circle, which serves as the aggregate root, and Member, representing members within the aggregate.
让我们从创建域层开始。 Circle 聚合由两个实体组成: Circle ,用作聚合根,以及 Member ,代表聚合内的成员。

pub struct Circle {
    pub id: CircleId, // Circle ID (Value Object)
    pub name: String,
    pub capacity: usize,
    pub owner: Member,
    pub members: Vec<Member>,
}
pub struct Member {
    pub id: MemberId, // Member ID (Value Object)
    pub name: String,
    pub age: usize,
    pub grade: Grade,
    pub major: Major,
}

We’re using Value Objects for IDs. I won't explain the difference between Entity and Value Object this time, so if you're interested, please look it up.
我们使用 Value Objects 作为 ID。这次我就不解释 Entity 和 Value Object 的区别了,有兴趣的话可以查一下。

use std::fmt;
use std::hash::{Hash, Hasher};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CircleId(usize);

impl CircleId {
    pub fn gen() -> Self {
        Self(rand::random::<usize>())
    }
}

impl std::convert::From<usize> for CircleId {
    fn from(id: usize) -> Self {
        Self(id)
    }
}

impl Hash for CircleId {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl fmt::Display for CircleId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::convert::From<CircleId> for usize {
    fn from(circle_id: CircleId) -> usize {
        circle_id.0
    }
}

Here’s the Rust code implementing methods for the domain layer, focusing on the Circle aggregate and the Member entity.
下面是领域层实现方法的 Rust 代码,重点关注 Circle 聚合和 Member 实体。

use crate::domain::aggregate::member::Member;
use crate::domain::aggregate::value_object::circle_id::CircleId;

use super::value_object::grade::Grade;
use anyhow::Error;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Circle {
    pub id: CircleId, // Circle ID (Value Object)
    pub name: String,
    pub capacity: usize,
    pub owner: Member,
    pub members: Vec<Member>,
}

impl Circle {
    // Method for creating a new circle
    pub fn new(name: String, owner: Member, capacity: usize) -> Result<Self, Error> {
        // Only 3rd graders can be owners
        if owner.grade != Grade::Third {
            return Err(Error::msg("Owner must be 3rd grade"));
        }

        // Circle must have at least 3 members
        if capacity < 3 {
            return Err(Error::msg("Circle capacity must be 3 or more"));
        }

        Ok(Circle {
            id: CircleId::gen(),
            name,
            owner,
            capacity,
            members: vec![],
        })
    }

    // Method for reconstructing a circle
    pub fn reconstruct(
        id: CircleId,
        name: String,
        owner: Member,
        capacity: usize,
        members: Vec<Member>,
    ) -> Self {
        Circle {
            id,
            name,
            owner,
            capacity,
            members,
        }
    }

    // Method for updating a circle
    pub fn update(&mut self, name: Option<String>, capacity: Option<usize>) {
        if let Some(name) = name {
            self.name = name;
        }
        if let Some(capacity) = capacity {
            self.capacity = capacity;
        };
    }

    // Method to check if the circle is full
    fn is_full(&self) -> bool {
        self.members.len() + 1 >= self.capacity
    }

    // Method to check if the circle can operate
    fn is_runnable(&self) -> bool {
        self.members.len() + 1 >= 3
    }

    // Method to check if a member can join a social gathering
    fn is_drinkable_alcohol(member: &Member) -> bool {
        member.is_adult()
    }

    // Method to add a member to the circle
    pub fn add_member(&mut self, member: Member) -> Result<(), Error> {
        // Can't add member if circle is full
        if self.is_full() {
            return Err(Error::msg("Circle member is full"));
        }

        // 4th graders can't join circles
        if member.grade == Grade::Fourth {
            return Err(Error::msg("4th grade can't join circle"));
        }

        self.members.push(member);
        Ok(())
    }

    // Method to remove a member from the circle
    pub fn remove_member(&mut self, member: &Member) -> Result<(), Error> {
        // Owner can't be removed
        if self.owner.id == member.id {
            return Err(Error::msg("Owner can't be removed"));
        }
        self.members.retain(|m| m.id != member.id);
        Ok(())
    }

    // Method to graduate 4th graders
    pub fn graduate(&mut self) {
        self.members.retain(|m| m.grade != Grade::Fourth);
    }
}
impl Member {
    // Method for creating a new member
    pub fn new(name: String, age: usize, grade: Grade, major: Major) -> Self {
        Member {
            id: MemberId::gen(),
            name,
            age,
            grade,
            major,
        }
    }

    // Method for reconstructing a member
    pub fn reconstruct(id: MemberId, name: String, age: usize, grade: Grade, major: Major) -> Self {
        Member {
            id,
            name,
            age,
            grade,
            major,
        }
    }

    // Method to check if the member is 20 or older
    pub fn is_adult(&self) -> bool {
        self.age >= 20
    }
}

This code includes methods for creating, reconstructing, updating, and managing members and circles in the domain layer.
该代码包括用于在领域层中创建、重建、更新和管理成员和圈子的方法。

Interfaces 接口

We create interfaces to expose the domain’s behavior externally. We’ll create an interface for manipulating the Circle aggregate.
我们创建接口来向外部公开域的行为。我们将创建一个用于操作 Circle 聚合的接口。

pub trait CircleRepositoryInterface {
    fn find_circle_by_id(&self, circle_id: &CircleId) -> Result<Circle, Error>;
    fn create(&self, circle: &Circle) -> Result<(), Error>;
    fn update(&self, circle: &Circle) -> Result<Circle, Error>;
    fn delete(&self, circle: &Circle) -> Result<(), Error>;
}

trait in Rust is similar to an interface in other languages.
Rust 中的 trait 与其他语言中的 interface 类似。

Infrastructure Layer 基础设施层

The infrastructure layer handles persistence. It doesn’t matter where the data is stored; it could be Firestore, Postgres, etc. For this example, we’ll store data in memory.
基础设施层处理持久性。数据存储在哪里并不重要;它可以是 Firestore、Postgres 等。在本例中,我们将数据存储在内存中。

In the infrastructure layer, we implement the interfaces from the domain layer.
在基础设施层,我们实现领域层的接口。

use anyhow::Error;

use crate::domain::{
    aggregate::{
        circle::Circle,
        member::Member,
        value_object::{circle_id::CircleId, grade::Grade, major::Major, member_id::MemberId},
    },
    interface::circle_repository_interface::CircleRepositoryInterface,
};

use super::db::Db;

#[derive(Clone, Debug)]
pub struct CircleRepository {
    db: Db,
}

impl CircleRepository {
    pub fn new() -> Self {
        Self { db: Db::new() }
    }
}

impl CircleRepositoryInterface for CircleRepository {
    fn find_circle_by_id(&self, circle_id: &CircleId) -> Result<Circle, Error> {
        match self.db.get::<CircleData, _>(&circle_id.to_string())? {
            Some(data) => Ok(Circle::try_from(data)?),
            None => Err(Error::msg("Circle not found")),
        }
    }

    fn create(&self, circle: &Circle) -> Result<(), Error> {
        match self.db.get::<CircleData, _>(&circle.id.to_string())? {
            Some(_) => Err(Error::msg("Circle already exists")),
            None => {
                self.db
                    .set(circle.id.to_string(), &CircleData::from(circle.clone()))?;
                Ok(())
            }
        }
    }

    fn update(&self, circle: &Circle) -> Result<Circle, Error> {
        match self.db.get::<CircleData, _>(&circle.id.to_string())? {
            Some(_) => self
                .db
                .set(circle.id.to_string(), &CircleData::from(circle.clone()))
                .and_then(|_| self.db.get::<CircleData, _>(&circle.id.to_string()))
                .map(|data| match data {
                    Some(data) => Circle::try_from(data),
                    None => Err(Error::msg("Failed to convert circle data")),
                })?,
            None => Err(Error::msg("Circle not found")),
        }
    }

    fn delete(&self, circle: &Circle) -> Result<(), Error> {
        match self.db.get::<CircleData, _>(&circle.id.to_string())? {
            Some(_) => self.db.remove(circle.id.to_string()),
            None => Err(Error::msg("Circle not found")),
        }
    }
}

#[derive(serde::Deserialize, serde::Serialize)]
struct CircleData {
    id: usize,
    name: String,
    owner: MemberData,
    capacity: usize,
    members: Vec<MemberData>,
}

impl std::convert::From<Circle> for CircleData {
    fn from(circle: Circle) -> Self {
        CircleData {
            id: circle.id.into(),
            name: circle.name,
            owner: MemberData::from(circle.owner),
            capacity: circle.capacity,
            members: circle.members.into_iter().map(MemberData::from).collect(),
        }
    }
}

impl std::convert::TryFrom<CircleData> for Circle {
    type Error = Error;

    fn try_from(data: CircleData) -> Result<Self, Self::Error> {
        Ok(Circle::reconstruct(
            CircleId::from(data.id),
            data.name,
            Member::reconstruct(
                MemberId::from(data.owner.id),
                data.owner.name,
                data.owner.age,
                Grade::try_from(data.owner.grade)?,
                Major::from(data.owner.major.as_str()),
            ),
            data.capacity,
            data.members
                .into_iter()
                .map(Member::try_from)
                .collect::<Result<Vec<Member>, Error>>()?,
        ))
    }
}

#[derive(serde::Deserialize, serde::Serialize)]
struct MemberData {
    id: usize,
    name: String,
    age: usize,
    grade: usize,
    major: String,
}

impl std::convert::From<Member> for MemberData {
    fn from(value: Member) -> Self {
        Self {
            id: value.id.into(),
            name: value.name,
            age: value.age,
            grade: value.grade.into(),
            major: value.major.into(),
        }
    }
}

impl std::convert::TryFrom<MemberData> for Member {
    type Error = Error;

    fn try_from(value: MemberData) -> Result<Self, Self::Error> {
        Ok(Member::reconstruct(
            MemberId::from(value.id),
            value.name,
            value.age,
            Grade::try_from(value.grade)?,
            Major::from(value.major.as_str()),
        ))
    }
}

We use XxxData to represent values retrieved from the database. In this case, it's CircleData and MemberData. We implement the TryFrom trait to convert these database types into domain layer types using the reconstruct method, allowing us to decouple the application layer from the infrastructure layer.
我们使用 XxxData 来表示从数据库检索的值。在本例中,它是 CircleData 和 MemberData 。我们实现 TryFrom 特征,使用 reconstruct 方法将这些数据库类型转换为域层类型,从而使我们能够将应用程序层与基础设施层解耦。

Exactly, abstracting away the database implementation details is crucial in Domain-Driven Design (DDD). This abstraction ensures that changes in the underlying database technology (like switching from a local in-memory database to Firebase or PostgreSQL) don’t affect the application layer. By depending on abstractions rather than concrete implementations, the application layer remains decoupled from the infrastructure layer.
确切地说,抽象出数据库实现细节在领域驱动设计(DDD)中至关重要。这种抽象确保底层数据库技术的更改(例如从本地内存数据库切换到 Firebase 或 PostgreSQL)不会影响应用程序层。通过依赖抽象而不是具体实现,应用程序层保持与基础设施层的解耦。

While the implementation of the database isn’t directly related to DDD concepts, it’s essential for ensuring that the domain logic can operate independently of specific database choices. If you’re interested, feel free to delve deeper into the database implementation details.
虽然数据库的实现与 DDD 概念没有直接关系,但它对于确保域逻辑可以独立于特定数据库选择进行操作至关重要。如果您有兴趣,请随意深入研究数据库实现细节。

use std::{
    collections::HashMap,
    sync::{Arc, RwLock},
};

#[derive(Clone, Debug)]
pub struct Db {
    db: Arc<RwLock<HashMap<String, String>>>,
}

impl Db {
    pub fn new() -> Self {
        Self {
            db: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    pub fn get<D, K>(&self, key: K) -> anyhow::Result<Option<D>>
    where
        K: AsRef<str>,
        D: serde::de::DeserializeOwned,
    {
        let db = self
            .db
            .read()
            .map_err(|e| anyhow::anyhow!("Error reading from database: {:?}", e))?;

        match db.get(key.as_ref()) {
            Some(value) => {
                let deserialized_value = serde_json::from_str(value)
                    .map_err(|e| anyhow::anyhow!("Error deserializing value: {:?}", e))?;
                Ok(Some(deserialized_value))
            }
            None => Ok(None),
        }
    }

    pub fn keys(&self) -> Vec<String> {
        let db = self.db.read().expect("read data from db");
        db.keys().cloned().collect()
    }

    pub fn remove<K>(&self, key: K) -> anyhow::Result<()>
    where
        K: AsRef<str>,
    {
        let mut db = self
            .db
            .write()
            .map_err(|e| anyhow::anyhow!("Error writing to database: {:?}", e))?;
        db.remove(key.as_ref())
            .ok_or_else(|| anyhow::anyhow!("Key not found in database"))?;
        Ok(())
    }

    pub fn set<S, K>(&self, key: K, value: &S) -> anyhow::Result<()>
    where
        K: Into<String>,
        S: serde::ser::Serialize,
    {
        let value = serde_json::to_string(value)?;
        let mut db = self
            .db
            .write()
            .map_err(|e| anyhow::anyhow!("Error writing to database: {:?}", e))?;
        db.insert(key.into(), value);
        Ok(())
    }
}

Application Layer 应用层

In the application layer, we use entities and value objects to achieve use cases and delegate processing to repositories (infrastructure layer). We request processing from repositories without directly depending on the infrastructure layer. This is known as the Dependency Inversion Principle, where we rely on abstractions rather than concrete implementations to realize use cases. In this context, we’ll implement the use case of creating a circle.
在应用程序层,我们使用实体和值对象来实现用例并将处理委托给存储库(基础设施层)。我们请求存储库进行处理,而不直接依赖于基础设施层。这称为依赖倒置原则,我们依靠抽象而不是具体实现来实现用例。在这种情况下,我们将实现创建圆圈的用例。

use anyhow::Result;
use serde::Deserialize;

use crate::domain::{
    aggregate::{
        circle::Circle,
        member::Member,
        value_object::{grade::Grade, major::Major},
    },
    interface::circle_repository_interface::CircleRepositoryInterface,
};

#[derive(Debug, Deserialize)]
pub struct CreateCircleInput {
    pub circle_name: String,
    pub capacity: usize,
    pub owner_name: String,
    pub owner_age: usize,
    pub owner_grade: usize,
    pub owner_major: String,
}

impl CreateCircleInput {
    pub fn new(
        circle_name: String,
        capacity: usize,
        owner_name: String,
        owner_age: usize,
        owner_grade: usize,
        owner_major: String,
    ) -> Self {
        CreateCircleInput {
            circle_name,
            capacity,
            owner_name,
            owner_age,
            owner_grade,
            owner_major,
        }
    }
}

#[derive(Debug, Deserialize)]
pub struct CreateCircleOutput {
    pub circle_id: usize,
    pub owner_id: usize,
}

pub struct CreateCircleUsecase<T>
where
    T: CircleRepositoryInterface,
{
    circle_repository: T,
}

impl<T> CreateCircleUsecase<T>
where
    T: CircleRepositoryInterface,
{
    pub fn new(circle_repository: T) -> Self {
        CreateCircleUsecase { circle_repository }
    }

    pub fn execute(
        &mut self,
        circle_circle_input: CreateCircleInput,
    ) -> Result<CreateCircleOutput> {
        let grade = Grade::try_from(circle_circle_input.owner_grade)?;

        let major = Major::from(circle_circle_input.owner_major.as_str());

        let owner = Member::new(
            circle_circle_input.owner_name,
            circle_circle_input.owner_age,
            grade,
            major,
        );
        let owner_id = owner.id;
        let circle = Circle::new(
            circle_circle_input.circle_name,
            owner,
            circle_circle_input.capacity,
        )?;
        self.circle_repository
            .create(&circle)
            .map(|_| CreateCircleOutput {
                circle_id: usize::from(circle.id),
                owner_id: usize::from(owner_id),
            })
    }
}

Use Case I/O (Input/Output): We define the I/O of the use case as CreateCircleInput and CreateCircleOutput.
用例 I/O(输入/输出):我们将用例的 I/O 定义为 CreateCircleInput 和 CreateCircleOutput 。

CreateCircleUsecase Struct: CreateCircleUsecase is a generic struct that receives something implementing the CircleRepositoryInterface trait as its field.
CreateCircleUsecase 结构: CreateCircleUsecase 是一个通用结构,它接收实现 CircleRepositoryInterface 特征的内容作为其字段。

Impl CreateCircleUsecase: We implement two methods, new and execute, for the CreateCircleUsecase struct.
Impl CreateCircleUsecase:我们为 CreateCircleUsecase 结构实现两个方法, new 和 execute 。

  • The new Method: This method generates instances and receives something implementing the CircleRepositoryInterface trait, akin to a constructor in other languages. Here, we inject dependencies using an abstraction (trait) instead of a concrete implementation, adhering to the Dependency Inversion Principle.
    new 方法:此方法生成实例并接收实现 CircleRepositoryInterface 特征的内容,类似于其他语言中的构造函数。在这里,我们使用抽象(特征)而不是具体实现来注入依赖项,遵循依赖倒置原则。
  • The execute Method: This method executes the use case. It takes CreateCircleInput, creates a Circle Entity, and saves it to the repository. self refers to the instance of CreateCircleUsecase, allowing us to access the field with self.circle_repository as it implements the CircleRepositoryInterface trait, enabling the create method call.
    execute 方法:该方法执行用例。它采用 CreateCircleInput ,创建一个 Circle 实体,并将其保存到存储库中。 self 引用 CreateCircleUsecase 的实例,允许我们使用 self.circle_repository 访问该字段,因为它实现了 CircleRepositoryInterface 特征,从而启用 < b6>方法调用。

This setup ensures that the use case depends solely on the domain and avoids dependencies on the infrastructure layer.
此设置可确保用例仅依赖于域,并避免依赖于基础设施层。

Presentation Layer 表示层

In the Presentation Layer, we define endpoints, handle request reception, manage responses, and map values to pass to the Application Layer.
在表示层中,我们定义端点、处理请求接收、管理响应以及映射值以传递到应用程序层。

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateCircleRequestBody {
    pub circle_name: String,
    pub capacity: usize,
    pub owner_name: String,
    pub owner_age: usize,
    pub owner_grade: usize,
    pub owner_major: String,
}

impl std::convert::From<CreateCircleRequestBody> for CreateCircleInput {
    fn from(
        CreateCircleRequestBody {
            circle_name,
            capacity,
            owner_name,
            owner_age,
            owner_grade,
            owner_major,
        }: CreateCircleRequestBody,
    ) -> Self {
        CreateCircleInput::new(
            circle_name,
            capacity,
            owner_name,
            owner_age,
            owner_grade,
            owner_major,
        )
    }
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateCircleResponseBody {
    pub circle_id: usize,
    pub owner_id: usize,
}

impl std::convert::From<CreateCircleOutput> for CreateCircleResponseBody {
    fn from(
        CreateCircleOutput {
            circle_id,
            owner_id,
        }: CreateCircleOutput,
    ) -> Self {
        CreateCircleResponseBody {
            circle_id,
            owner_id,
        }
    }
}

pub async fn handle_create_circle(
    State(state): State<AppState>,
    Json(body): Json<CreateCircleRequestBody>,
) -> Result<Json<CreateCircleResponseBody>, String> {
    let circle_circle_input = CreateCircleInput::from(body);
    let mut usecase = CreateCircleUsecase::new(state.circle_repository);
    usecase
        .execute(circle_circle_input)
        .map(CreateCircleResponseBody::from)
        .map(Json)
        .map_err(|e| e.to_string())
}

#[derive(Clone)]
struct AppState {
    circle_repository: CircleRepository,
}

fn router() -> Router<AppState> {
    Router::new()
        .route("/circle", post(handle_create_circle))
}

#[tokio::main]
async fn main() -> Result<(), ()> {
    let state = AppState {
        circle_repository: CircleRepository::new(),
    };

    let app = router().with_state(state);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("Listening on: {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
    Ok(())
}

Input/Output (I/O): CreateCircleRequestBody is a struct used to receive the request body. We implement From for CreateCircleRequestBody to construct CreateCircleInput. The response is implemented similarly.
输入/输出(I/O): CreateCircleRequestBody 是用于接收请求正文的结构体。我们为 CreateCircleRequestBody 实现 From 来构造 CreateCircleInput 。响应的实现类似。

AppState: AppState represents the application's state, provided by axum. It holds the repository and acts as a Dependency Injection (DI) container for resolving dependencies. While there's only one dependency in this case, it provides flexibility such as swapping CircleRepository with a mock like CircleRepositoryMock for testing purposes.
AppState: AppState 代表应用程序的状态,由axum提供。它保存存储库并充当用于解决依赖关系的依赖注入 (DI) 容器。虽然在这种情况下只有一个依赖项,但它提供了灵活性,例如将 CircleRepository 与 CircleRepositoryMock 等模拟交换以进行测试。

handle_create_circle: This function receives the request and executes CreateCircleUsecase. The State(state) in the first argument retrieves the value from AppState, which is then injected into CreateCircleUsecase to call the execute method, as mentioned earlier. I/O is converted to desired formats accordingly.
handle_create_circle :该函数接收请求并执行 CreateCircleUsecase 。第一个参数中的 State(state) 从 AppState 检索值,然后将其注入到 CreateCircleUsecase 中以调用 execute 方法,如上所述早些时候。 I/O 相应地转换为所需的格式。

I tried implementing an API server using Rust, Axum, and Domain-Driven Design (DDD). While Rust adoption in business is still relatively low, I believe it will gain traction, especially in backend development. I encourage you to give it a try. Feel free to contribute by adding new use cases or aggregates to further aid learning.
我尝试使用 Rust、Axum 和领域驱动设计 (DDD) 来实现 API 服务器。虽然 Rust 在商业中的采用率仍然相对较低,但我相信它将获得牵引力,尤其是在后端开发方面。我鼓励你尝试一下。请随意添加新的用例或聚合来进一步帮助学习。


您可以在此处找到该项目的源代码。

learning-rust/src/hello.rs at io-uring · amacal/learning-rust · GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/547505.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年山东三支一扶考试报名照片要求

2024年山东三支一扶考试报名照片要求

(九)C++自制植物大战僵尸游戏自定义对话框的实现

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/m0EtD 对话框在游戏的交互中非常重要。在游戏中&#xff0c;对话框不仅可以提醒用户下达任务指令&#xff0c;而且还可以让用户进行操作&#xff0c;自定义游戏中的各种属性。对话框在游戏的交互中非常常见且大量使用。Co…

开源相机管理库Aravis例程学习(三)——注册回调multiple-acquisition-callback

开源相机管理库Aravis例程学习&#xff08;三&#xff09;——回调multiple-acquisition-callback 简介例程代码arv_camera_create_streamArvStreamCallbackTypeArvStreamCallback 简介 本文针对官方例程中的&#xff1a;02-multiple-acquisition-callback做简单的讲解。 ara…

[已解决]问题:root.users.hdfs is not a leaf queue

问题&#xff1a;root.users.hdfs is not a leaf queue CDH集群报错&#xff1a; Exception in thread “main” org.apache.hadoop.yarn.exceptions.YarnException: Failed to submit application_1713149630679_0005 to YARN : root.users.hdfs is not a leaf queue 思路 …

ActiveMQ 任意文件上传漏洞复现

一、使用弱口令登陆 ​ 访问 http://ip:8161/admin/ 进入admin登陆页面&#xff0c;使用弱口令登陆&#xff0c;账号密码皆为 admin&#xff0c;登陆成功后&#xff0c;headers中会出现验证信息 ​ 如&#xff1a; Authorization: Basic YWRtaW46YWRtaW4 # 二、利用PUT协议上…

个人投资理财入门

1.简单而言&#xff0c;所谓个人理财&#xff0c;是为了实现个人的人生目标和理想而制订、安排、实施和管理一个各方面总体协调的财务计划的过程。更加直白点儿说&#xff0c;理财就是打理钱财&#xff0c;就是赚钱、省钱、花钱之道。 2.根据经济学上的定义&#xff0c;投资是指…

Unity AR开发环境搭建

在这个项目中使用 Unity 2022.3.19。 AR项目建议使用2022.3及以上版本。 创建一个 3D URP 项目并将其命名为 Magicbox-AR。 注意&#xff1a;如果计划发布 iOS 版 AR 项目&#xff0c;则必须有权使用 Mac 进行最终构建。Windows 计算机无法为 iOS 设备构建最终产品。 项目创建…

[ROS 系列学习教程] 建模与仿真 - URDF 语法介绍

ROS 系列学习教程(总目录) 本文目录 一、robot标签二、link标签三、joint标签 URDF文件中使用XML格式描述的机器人模型&#xff0c;下面介绍URDF的XML标签。 一、robot标签 机器人描述文件中的根元素必须是robot&#xff0c;所有其他元素必须封装在其中。 属性 name&#x…

Springboot引入外部jar包并打包jar包

前言 spring boot项目开发过程中难免需要引入外部jar包&#xff0c;下面将以idea为例说明操作步骤 将需要的jar包导入到项目中 2.在maven中引入jar包 <dependency><groupId>com</groupId><!--随便填的文件夹名称--><artifactId>xxx</artif…

【C++学习】C++4种类型转换详解

这里写目录标题 &#x1f680;C语言中的类型转换&#x1f680;为什么C需要四种类型转换&#x1f680;C强制类型转换&#x1f680;static_cast&#x1f680;**reinterpret_cast**&#x1f680;const_cast与volatile&#x1f680;dynamic_cast &#x1f680;C语言中的类型转换 在…

百度 千帆sdk 试用

主要是Java SDK的使用&#xff1a; <dependency> <groupId>com.baidubce</groupId> <artifactId>qianfan</artifactId> <version>0.0.4</version> </dependency> 参考文档&#xff1a;bce-qianfan-sdk/java at main baidub…

用于 SQLite 的异步 I/O 模块(二十四)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite的PRAGMA 声明&#xff08;二十三&#xff09; 下一篇&#xff1a;SQLite、MySQL 和 PostgreSQL 数据库速度比较&#xff08;本文阐述时间很早比较&#xff0c;不具有最新参考性&#xff09;&#xff08;二…

一些实用的工具网站

200 css渐变底色 https://webgradients.com/ 200动画效果复制 https://css-loaders.com/classic/ 二次贝塞尔曲线 https://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html 三次贝塞尔曲线 https://blogs.sitepointstatic.com/examples/tech/c…

PID c++算法学习和实现

原理图&#xff1a; &#xff08;1&#xff09;位置式PID 是1&#xff1a;当前系统的实际位置&#xff0c;与你想要达到的预期位置的偏差&#xff0c; 2&#xff1a;进行PID控制,误差会一直累加&#xff0c;会使当前输出与过去的所有输入相关&#xff0c;输入uk出错&#xff…

python将pdf转为docx

如何使用python实现将pdf文件转为docx文件 1.首先要安装pdf2docx库 pip install pdf2docx2.实现转换 from pdf2docx import Converterdef convert_pdf_to_docx(input_pdf, output_docx):# 创建一个PDF转换器对象pdf_converter Converter(input_pdf)# 将PDF转换为docx文件pdf…

【技术干货】长运通SiP微模块技术介绍

一、什么是SiP技术&#xff1f; SiP原文“System in Package”&#xff0c;字面含义是系统级封装&#xff0c;国际半导体技术发展路线图 ( ITRS 2005 )对 SiP 的定义是&#xff1a;系统级封装是采用任何组合, 将多个具有不同功能的有源电子器件与可选择性的无源元件以及诸如 M…

解决CSS中鼠标移入到某个元素其子元素被遮挡的问题

我们在开发中经常遇到一种场景&#xff0c;就是给元素加提示信息&#xff0c;就是鼠标移入到盒子上面时&#xff0c;会出现提示信息这一功能&#xff0c;如果我们给盒子加了hover&#xff0c;当鼠标移入到盒子上时&#xff0c;让他往上移动5px&#xff0c;即transform: transla…

2024年下载排行第一的数据恢复软件EasyRecovery

EasyRecovery数据恢复软件是一款功能强大的数据恢复工具&#xff0c;它能够帮助用户恢复因各种原因丢失的数据&#xff0c;无论是误删除、格式化、分区丢失还是其他存储介质故障&#xff0c;都能得到很好的解决。 使用EasyRecovery进行数据恢复&#xff0c;用户只需按照简单的…

租用境外服务器,越南服务器的优势有哪些

自从中国加入世界贸易组织之后&#xff0c;国内经济增加速度非常快&#xff0c;同时越来越多的人选择去东南亚国家发展&#xff0c;因为当地的中国人很多&#xff0c;所以中国企业在当地面临着更小的文化差异。东南亚地区也是最新的经济体&#xff0c;互联网正处于蓬勃发展的阶…

【日志跟踪】SpringBoot实现简单的日志链路追踪

引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j&l…