Rust Web全栈开发之旅

497 阅读4分钟

前言

本文阅读耗时1分钟

学习B站视频《Rust Web全栈开发教程》后做的笔记(WebAppWebAssemblyWebService),课程源码见Rust Web全栈开发源码

参考资料:
Rust的Web开发
WebAssembly
Rust and WebAssembly中文
Rust and WebAssembly
postgreSQL使用

目录

Rust Web全栈开发之旅.png

一、WebApp

uml_01.png

二、WebAssembly

uml_02.png

三、WebService

uml_03.png

四、总结

部分知识点

derive

编译器可以通过#[derive]为一些trait提供基础的实现。 如果需要更复杂的逻辑,这些trait也可以被手动实现

#[derive(Serialize, Debug, Clone, sqlx::FromRow)]
pub struct Course {
    pub teacher_id: i32,
    pub id: i32,
    pub name: String,
    pub time: Option<NaiveDateTime>,
    pub description: Option<String>,
    pub format: Option<String>,
    pub structure: Option<String>,
    pub duration: Option<String>,
    pub price: Option<i32>,
    pub language: Option<String>,
    pub level: Option<String>,
}

它就实现了clone接口和序列化成字符串接口。否则我们自己要去手动impl 这些trait。相当于类型转换的规则

  • 例如这些都编译器为基础类型实现了的
    • 比较:Eq、PartialEq、Ord、PartialOrd
    • Clone:从&T的一个拷贝创建T
    • Copy:把一个类型的move转换为copy
    • Hash:从&T计算它的哈希
    • Default:创建一个数据类型的空实例
    • Debug: 用{:?}格式化一个值

例如加了 sqlx::FromRow 这个,在我们读取数据库的时候直接转换映射成我们标记的数据模型类型

自定义值之间的转换

Json<UpdateCourse> 类型转换成 UpdateCourse类型

impl From<web::Json<UpdateCourse>> for UpdateCourse{
    fn from(course: web::Json<UpdateCourse>) -> Self{
        UpdateCourse{
            name: course.name.clone(),
            description:course.description.clone(),
            format: course.format.clone(),
            structure: course.structure.clone(),
            duration: course.duration.clone(),
            price: course.price,
            language: course.language.clone(),
            level: course.level.clone(),
        }
    }
}

调用触发转换

update_course: web::Json<UpdateCourse>;
let a = update_course.into(); //a的类型就是UpdateCourse

或者可以利用try_from,可以抛出转换时的异常

impl TryFrom<web::Json<CreateCourse>> for CreateCourse {
    type Error = MyError;
    fn try_from(course: web::Json<CreateCourse>) -> Result<Self, Self::Error> {
        Ok(CreateCourse {
            teacher_id: course.teacher_id,
            name: course.name.clone(),
            description: course.description.clone(),
            format: course.format.clone(),
            structure: course.structure.clone(),
            duration: course.duration.clone(),
            price: course.price,
            language: course.language.clone(),
            level: course.level.clone(),
        })
    }
}

触发调用

new_course: web::Json<CreateCourse>;
new_course.try_into()? ;//这样才是CreateCourse,如果有异常机会跑出去

代码中的?

    let rows  = sqlx::query!(r#"SELECT id, name, picture_url, profile FROM teacher"#)
        .fetch_all(pool)
        .await?;

这个rowsVec<Record>类型。但是如果不加呢,它就是Result<Vec<Record>>, Error>类型。加了,如果这时候遇到错误了,会直接返回错误,抛到上层的。

格式化输出

我们想这样输出println!("{}",error),但是非基础类型没有实现Display trait,该怎么做呢?

impl fmt::Display for MyError{
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{
        write!(f, "{}", self)
    }
}

包括其他的,例如Debug trait同理,自己实现一下,相当于类型转换的实现。

Web全栈开发中遇到的问题

  • 数据库url连接失败导致编译失败

     dotenv().ok();
        let database_url = env::var("DATABASE_URL").expect("DATABASE_URL 没有在 .env文件里设置");
        let db_pool = PgPoolOptions::new().connect(&database_url).await.unwrap();
    

    在写代码的时候,Rust会主动去连接数据库,只要连接失败了,然后后面代码中利用 dp_pool去查询等操作会报计算机拒绝访问等问题。

  • 数据库表不存在或者字段错误导致编译失败
    数据库中没有创建表,然后代码里又操作了这张表,会报红,也编译不了。或者字段错误也编译不了。这些都是编译前,在IDE中自己会强制报红。
    前提是数据库服务一定要先起来,否则你永远也编译不过代码。

  • 属性不匹配导致编译失败问题

    pub async fn get_teacher_details_db(pool: &PgPool, teacher_id: i32) -> Result<Teacher, MyError> {
        let row = sqlx::query!(
            "SELECT id, name, picture_url, profile FROM teacher where id = $1",
            teacher_id
        )
        .fetch_one(pool)
        .await
        .map(|r| Teacher {
            id: r.id,
            name: r.name, //这里r是Record类型,但是是可以直接用的,具备语义化
            picture_url: r.picture_url, //实际这些字段都报赋值失败
            profile: r.profile,
        })
        .map_err(|_err| MyError::NotFound("Teacher Id not found".into()))?;
        Ok(row)
    }
    

上述报错,String不能直接使用Option<String>赋值。
报错原因:因为数据库中teacher表创建的时候,namepicture_urlprofile没有指定not null,导致语义化后的类型是Option<String>,包含了None选择。

  • 访问失败问题

    //为啥不是 localhost:3000/courses
    pub fn course_routes(cfg: &mut web::ServiceConfig) {
        cfg.service(web::scope("/courses"))
            .route("/{teacher_id}/{course_id}", web::get().to(get_course_detail));
    }
    

    为啥不是 访问localhost:3000/能达到效果,因为限制域后的route写错了,改成

    pub fn course_routes(cfg: &mut web::ServiceConfig) {
        cfg.service(web::scope("/courses")
                        .route("/{teacher_id}/{course_id}", web::get().to(get_course_detail))
            );
    }
    

开发完后的感受

性能肯定是比Java好的,Rust的性能和体积优势很明显,Arctix框架性能web中榜首,单纯HelloWorld项目而言,能达到1.6k,没有深入研究对比,这里不作评价。网上也有很多做了比较,例如 Rust Web性能对比
但是作为新上手的开发来说,写起来成本会比较高,因为你在写代码的时候,好多问题都会在编译前提示你去解决,否则编译不过。到最后编译成功,可以说基本没有太多语法、内存上的BUG了。
在写代码过程中,更加体会到了Rust大多采用宏、函数式编程范式实现, 有点会让一些从JAVAC之类语言入门的程序员感到不适应,Rust没有继承,只有一套Trait的定义,这个对应Java的接口。
Rust WebAssembly 前端开发比较繁琐,抛开性能来说,还不如直接用js。因为对于对于公司、团队来说,开发时间,简洁性、可维护性,这几个才有价值,而Rust在前端的应用,学习成本和维护成本太高。但是对于后端而言,工具链完善,间接和可维护性我觉得属于还不错,前期开发时间可能会长一点,但是以后大概率会成为趋势。