likes
comments
collection
share

Rust入门教学+实战《使用axum实现一个http server》

作者站长头像
站长
· 阅读数 24

前言

Rust最近两年很活跃,作为走在时尚前沿的前端er,了解一下还是很有必要的,毕竟技多不压身嘛。 为了让大家快速理解,我会结合TypeScript做类比,帮助大家更好的理解。因为我本身也是刚学不久,有错误的地方,欢迎大家指出。 如果觉得对你有帮助的话,帮我点点赞。

Rust特点

语言层面

Rust 通过结合高性能和内存安全,提供了一个适合系统编程、嵌入式编程和并发编程的强大工具。其所有权系统和借用检查器在编译时捕获了许多常见的编程错误,使得开发者可以编写高效且安全的代码。

  • Rust 速度惊人且内存利用率极高(由于没有运行时和垃圾回收)
  • Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。
  • 包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持

使用领域

命令行、WebAssembly 、网络应用 、嵌入式应用等,都可以使用Rust构建。

hello world

环境安装

mac电脑可以直接通过brew安装

brew install rust

安装之后执行

rustc --version
cargo --version

安装详情:learnku.com/rust/wikis/…

rust和cargo,就像node和npm的关系。

初始化项目

cargo init 项目名

├── Cargo.toml
├── src
│   └── main.rs

Cargo.toml类似于package.jsonsrc/main.rs就是默认的入口文件,类似package.jsonmain字段。 (其实就是进程的主线程)

dev运行

cargo run (类似于npm run dev或者npm run start) 结果如下:

Hello, world!

运行成功,至此rust这门语言我们已经掌握50%了😄。

开发编辑器

编辑器vscodeRustRover(推荐这个,jetbrain免费的,不需要安装很多插件)

语法速讲

ps: 语法讲解方面,我只把一些关键的特性讲一下,其他方面需要你自己探索了,主打的是一个“一笔带过,不求甚解。”

基础语法

变量

类型

rust代码中类型,不强制开发者提供,未提供的编译器会做类型推断。 如下:s和s1的类型都为String

let s = String::from("I am  xxx");
let s1:String = String::from("I am  xxx");

rust类型推断非常强大,不单单是错误校验,有时候还会影响运行结果。 这点后文会提到的

声明与修改

声明变量使用关键字let

fn main() {
    let a = "asdasda";
    println!("{}",a);
}

注意:每一行代码结束强制使用分号;

执行cargo run(后文,不做特殊说明都是这个命令) 不过let和js的let并不一样的,默认情况下,let有点像js const,声明之后不能改,不过可以声明多次。 如下

fn main() {
   let a = "1";
    println!("{}", a);
    let a = "2";
    println!("{}", a);
    let a = "3";
    println!("{}", a);
    // 会报错
    // a = "4"
}

想要修改怎么办? 使用mut关键字

fn main() {
   let mut a = "1";
    println!("{}", a);
    a = "2";
    println!("{}", a);
    // a = "1";
}
所有权

这个是Rust的一个难点之一

所有权其实就是,某一段内存只能归属于一个变量,如果复制给另一个变量,就会发生所有权转移。(这点其实和变量的生命周期有关,js里面的闭包就会存在这种情况,函数执行结束了,变量还没有被回收。rust因为没有gc(垃圾回收),通过引入所有权和生命周期来控制内存的开销)

看两个例子

fn main() {
    let a = 10;
    let b = a;
    println!("{a}");
    println!("{b}");
}

打印两个 10,没有任何问题

下面这个例子就有点问题了

fn main() {
    let s1 = String::from("I am a superman.");
    let s2 = s1;
    println!("{s1}");
    println!("{s2}");
}

对于固定尺寸类型,会默认放在栈上;而非固定尺寸类型,会默认创建在堆上,成为堆上的一个资源,然后在栈上用一个局部变量来指向它。

这里和js的浅拷贝与深拷贝有点像。不过在let s2 = s1;会将这段内存的所有权从s1转移给了s2

变量作为实参也会所有权发生转移:

fn foo(s: String) {
    println!("{s}");
}

fn main() {
    let s1 = String::from("I am  xxx");
    foo(s1);
    println!("{s1}");    // 这里加了一行
}

上面这段代码将会报错。

引用

“引用”这词,我们在回答面试题的时候,都回答过好多遍了。 计算机术语中定义如下

引用是一个数据对象,它包含了另一个对象的位置(地址)。引用本身并不直接包含数据,而是指向数据的存储位置。

在js中我们只知道某个行为是引用造成(比如浅拷贝),没有使用过,属于是看不见摸不着。rust中可以使用引用。

上一节的例子,我们改成这样就可以运行了:

fn main() {
    let s1 = String::from("I am  xxx");
    let s2 = &s1;   // 这里加了一行
    println!("{}", s1);
    println!("{}", s2);
}

引用不会发生所有权转换,只是指向存储地址。 这个例子引用其实有点像es6的getter。 这时候有同学就要问了是不是还有setter? 就是我们想通过改变引用,改变原来内存的值。正常情况下肯定是不会让你修改,就像你通过es6设置了getter一样,想改变值是不是还得设置setter,rust想改变原来的值的行为就叫解引用

fn main() {
    let mut a = 10;
    let b = &mut a;
    *b = 20;
    println!("{a}");  // 加了一句打印语句
}

注意&mut 表示可变引用。

问题:

下面两个例子加一行代码:都会报错 例子1

fn main() {
    let mut a = 10;
    let b = &mut a;
    *b = 20;
    println!("{a}");  // 加了一句打印语句
    a = 30;
    println!("{b}");  // 加了一句打
}

例子2

fn main() {
    let mut a = 10;
    let b = &mut a;
    *b = 20;
    println!("{a}");  // 加了一句打印语句
    println!("{b}");  // 加了一句打
}

会报错 这里我们记住一条规律:对于同一个内存(一开始定义的变量a)读写权限不能交叉。如例子2 a=10到打印a(读),交叉了声明b打印b,所以会报错,感兴趣的可以自己研究一下。

字符串

在了解rust的字符串之后,你就发现js字符串背后做了很多的事情。 字符串单引号和双引号对值而言没有区别。 看下面这段代码:

let a = '123'
console.log(a)
a.includes(2)

打印a的时候会是一个字符串原始类型,但是a.include()又当成对象在使用了。 (js引擎在背后帮我们做了封箱操作)。

单双引号

看下面这段代码:

fn main() {
    let a = '2';
    let b = "abc";
}

单引号声明的类型为char类型,且长度只能一位。 双引号声明的为&str

字符串类型转换

这里我们重点讲一下双引号字符串的关系,看个例子。

fn main() {
    let s1:&str = "I am a superman.";
    let s2: String = s1.to_string();
    println!("{s2}")
}

str表示静态数据(也叫字符串切片),写在内存上的一段静态数据,比如字符串“abc”。

&str就好理解了,指向“abc”的引用。

&str具有to_string方法,调用的话变成String类型。

对象

如下为声明一个对象(rust称为结构)

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {                // 就像这样去实现
    fn area(self) -> u32 {      // area就是方法,被放在impl实现体中
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("{:?}", rect1);
    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()      // 使用点号操作符调用area方法
    );
    // Rectangle { width: 30, height: 50 }
    // The area of the rectangle is 1500 square pixels.


}

这里有些知识点需要讲一下

  • #[derive(Debug)]为宏,和ts的装饰器很像,就是安装隐形的翅膀。不过宏是编译阶段(可以理解为帮你把代码写好),装饰器是运行时的。
  • struct只能定义属性,定义方法通过impl关键字,有点像js对象的原型。
  • rust使用组合实现了继承而并非面向对象的那种。
  • 方法中也有带有!,其实也是宏。
  • println!("{:?}", rect1);{:?}为debug占位符。

关于宏,后面会讲到。这里先不展开。

构造函数

想自定义构造函数可以这样写:

impl Rectangle {
  pub fn new(width: u32, height: u32) -> Self {
    Rectangle {
        width,
        height,
    }
  }  
}

调用构造函数的方式为Rectangle::new(10,10)

pub类似于es6的static关键字,不过调用方式有些差异。 上文提到的String::from,from就是pub方法。

枚举与模式匹配

看下面一段代码:

enum Number { Zero = 0, One = 1, Two = 2}

fn main() {
    let a = Number::One;

   match a {
      Number::One => {
          println!("1");
      },
       Number::Two => {
           println!("2");
       },
       Number::Zero => {
           println!("0");
       }
   }


}

又有非常多的知识点需要我们学习了:

  • enum Number定义一个枚举。这点和ts很像。
  • match就是模式匹配,类似于jsswitch case,不过作为强类型语言需要你处理所有的可能情况(毕竟要跑在系统底层的代码,这点后面很多语法都是如此)。比如我们将Number::Zero => { println!("0"); }移除就会编译不通过。

我们上面也说过类似于js switch, 可以使用_关键字达到default的效果。_在模式匹配这里就是通配符的作用。

enum Number { Zero = 0, One = 1, Two = 2}

fn main() {
    let a = Number::One;

   match a {
      Number::One => {
          println!("1");
      },
       _ => {
           println!("else");
       }
   }


}
Option

rust中Option是一个非常关键的枚举

定义如下:

pub enum Option<T> {
    None,
    Some(T),
}

T就是类似于ts的泛型

有点类似于我在ts中定义某个类型: function fn(a?:string){},(a为undefind或者string) 或者是ts的Option util type

Option为枚举取值为None或者Some

看一下这段代码:

fn main() {
    let vec = vec![1, 2, 3, 4, 5];
    
    let result = vec.iter().find(|&&x| x % 2 == 0);
    
    match result {
        Some(&x) => println!("Found an even number: {}", x),
        None => println!("No even number found"),
    }
}

又有新的知识点了

  • vec表示动态数组(动态的意思是:长度可变的);
  • find类似于js的数组find
  • 变量result的类型就是Option<&i32>,find可能找到了,也可能没有找到。和js不同,result的取值要么是null,要么就是找到的那个元素。rust不同,返回一个Option类型的值
  • 我们通过模式匹配检测结果。

在ts中我们能确定那个元素一定存在,可以使用“!”(告诉编译器一定存在的) :

let arr = [1,2,3]
arr.find(v => v ===1)!.

rust中也有类似的操作,我们称为解包 可以使用unwrap,或者其他api:

fn main() {
    let vec = vec![1, 2, 3, 4, 5];

    let result = vec.iter().find(|&&x| x % 2 == 0).unwrap();

}

这样result的类型就变成了&i32,数组元素的类型了。

unwrap称为:解包。(是不是有点像薛定谔的猫,必须要打开盒子才知道 猫是死(None)的还是活的(Some))。

还有内置的Result枚举类型,就不展开了,感兴趣的可以自行了解。

元组

长度固定的数组,类似于ts tuple

let arr = [1,2,3]

约束

trait

trait在类型约束方面类似于ts的interface,但又能力不仅于此。

看一段代码:

struct Point<T> {
    x: T,
    y: T,
}

fn print<td::fmt::Display>(p: Point<T>) {
    println!("Point {}, {}", p.x, p.y);
}

fn main() {
    let p = Point {x: 10, y: 20};
    print(p);

    let p = Point {x: 10.2, y: 20.4};
    print(p);
}

知识点又来了

  • Point<T> T类似于ts泛型,使用时可以指定类型。
  • td::fmt::Display为一个trait,print函数对T进行了约束,T必须实现了Display,x=10i32可以直接使用println!()方法, rust帮我们实现了这个trait;

下面这样调用就会报错:

println!("Point{}",p);

可以为point实现 Display trait

use std::fmt;

// 定义 Point 结构体
struct Point<T> {
    x: T,
    y: T,
}

// 为 Point<T> 实现 Display trait,其中 T 需要实现 Display trait
impl<T: fmt::Display> fmt::Display for Point<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

fn main() {
    // 测试整数类型的 Point
    let int_point = Point { x: 5, y: 10 };
    println!("{}", int_point); // 输出: (5, 10)
}

我们也可以通过调用 debug宏,debug宏我们上文提到在编译时帮我们填充代码的,其实就是自动实现了Debug trait

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}
fn main() {
    let p = Point {x: 10, y: 20};
    print(p);


    let p = Point {x: 10.2, y: 20.4};
    // print(p);

    println!("Point{:?}",p);
}

前面我们提到了,rust使用组合实现了类似于面向对象的效果。 trait也可以组合:

T: TraitA + TraitB + TraitC + TraitD

类似于ts的 &,type T = A & B & C

条件表达式:

if

fn main() {
    let number = 5;

    if number > 0 {
        println!("The number is positive.");
    } else if number < 0 {
        println!("The number is negative.");
    } else {
        println!("The number is zero.");
    }
}

if 比较简单,和js类似。

match

fn main() {
    let number = 7;

    match number {
        1 | 2 | 3 => println!("One, Two, or Three!"),
        4..=6 => println!("Four to Six!"),
        _ => println!("Other!"),
    }
}

match比较灵活。

遍历

for

数组
fn main() {
    let array = [1, 2, 3, 4, 5];

    for element in array.iter() {
        println!("Element: {}", element);
    }
}
Range范围
fn main() {
    // 使用 for 循环遍历区间 1..10
    for i in 1..10 {
        println!("{}", i);
    }
}

1..10 的类似其实是个Range类型,可以使用for循环迭代。

while

fn main() {
    let array = [1, 2, 3, 4, 5];
    let mut index = 0;

    while index < array.len() {
        println!("Element: {}", array[index]);
        index += 1;
    }
}

类型推断

看下面的代码:翻转一个字符串。

fn main() {
    let original = "hello, world!";
    let reversed: String = original.chars().rev().collect();
    let reversed2: Vec<char>= original.chars().rev().collect();
    println!("Original: {}", original);
    println!("Reversed: {}", reversed);
    println!("Reversed2: {:?}", reversed2);
}

打印结果为:

Original: hello, world!
Reversed: !dlrow ,olleh
Reversed2: ['!', 'd', 'l', 'r', 'o', 'w', ' ', ',', 'o', 'l', 'l', 'e', 'h']

其中reversedreversed2操作完全相同,只是标注的变量类型不同,返回结果却差别很大,这样这是rust的编译器的强大之处,不单单是类型约束!

宏的解析和执行是在 Rust 代码的编译阶段之前。你可以理解成,在 Rust 代码编译之前有一个宏展开的过程,这个过程的输出结果就是完整版的 Rust 代码,然后 Rust 编译器再来编译这个输出的代码。

声明宏

vec!就是一个声明宏

let v = vec![1,2,3];

展开为

{
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
}

过程宏

派生宏

对于派生这个词我们并不陌生,比如es6关键词extends或者ts的interface extends,就是继承。前面我们也提到多次,rust通过组合实现继承。

上文中的#[derive(Debug)]就是派生宏。

属性宏

只在单元测试时生效

// 声明下面这个函数为单元测试函数,这个属性只作用在test_foo()函数上
#[test]
fn test_foo() { 
/* ... */
}
函数宏

在 Rust 中,函数宏(Function-like Macros)是一种允许开发者定义宏看起来像函数调用的语法结构。它们接受输入并生成代码,通常用于代码生成、模板化和元编程。函数宏的定义和使用在 Rust 中非常灵活且强大。

看一段代码:

// 这是伪代码
use sql_macros::sqlbuilder;

fn foo() {
    sqlbuilder!(select title, content from article where id='1111111';);
}

上面的select title, content from article where id='1111111';)非rust语法,通过宏的能力,可以让我们书写sql非常舒服,不需要做字符串拼接了。

http-server

tokio

Tokio 是一个用于异步编程的 Rust 库。它提供了必要的工具和运行时,用于构建高性能的异步 I/O 应用程序,例如网络服务器、客户端、和其他需要并发处理的应用。Tokio 的设计目标是高效地处理大量 I/O 操作,同时保持较低的延迟和高吞吐量。

看一段代码:

use tokio::fs::File;
use tokio::io::AsyncWriteExt;     // 引入AsyncWriteExt trait

async fn doit() -> std::io::Result<()> {             
    let mut file = File::create("foo.txt").await.unwrap();  // 创建文件
    file.write_all(b"hello, world!").await.unwrap();        // 写入内容
    Ok(())
}

#[tokio::main]
async fn main() {
    let result = doit().await;   // 注意这里的.await
}

上面的这段代码的作用是:创建文本文件、写入文本到文件。

代码风格是不是很熟悉?很像js的async await,事实也是如此,确实很像。 不过rust本身并不支持这种语法,tokio在底层帮我们做了很多事情。

#[tokio::main]      // 这个是tokio库里面提供的一个属性宏标注
async fn main() {   // 注意 main 函数前面有 async 
    println!("Hello world");
}

展开之后:

fn main() {
    tokio::runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async {              // 注意这里block_on,里面是异步代码
            println!("Hello world");
        })
}

上面这段代码,我们看函数名能略知一二:new_multi_thread() 起多线程来这些任务。和nodejs的异步api很像(ps: node启动时,会启动一个线程池,有异步任务了,就会在线程池里面取一个线程,来执行任务,执行完成了通知(kqueue、epoll等系统级别通知)主线程 ,nginx也是如此)。

Axum

Axum 是一个基于 Tokio 的高性能异步 HTTP 服务器框架,用于构建 Rust 中的 Web 应用程序和 API。Axum 旨在提供一种简单、灵活且高效的方式来构建现代 Web 服务,充分利用 Rust 的所有权模型和类型系统来实现安全且高效的代码。

hello world

src/main.rs

use axum::{        // 引入 Axum 库
    routing::get,  // 引入用于定义 GET 路由的模块
    Router,        // 引入 Router 类型,用于构建路由器
};
use std::net::SocketAddr; // 引入标准库中的 SocketAddr 类型,用于定义服务器的监听地址
use tokio::main;          // 引入 Tokio 库中的 main 宏,用于定义异步主函数

#[main]                   // 使用 Tokio 的 main 宏,表示该函数是异步的主函数
async fn main() {
    // 构建路由器,将根路径 "/" 映射到 hello_world 处理程序
    let app = Router::new().route("/", get(hello_world));

    // 定义服务器的监听地址,127.0.0.1 表示本地主机,端口号为 3000
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    // 打印服务器的监听地址
    println!("Listening on http://{}", addr);

    // 启动服务器并开始监听指定的地址,将请求路由到相应的处理程序
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await        // 等待服务器异步运行
        .unwrap();    // 如果服务器运行过程中出现错误,将其解包并 panic
}

// 定义一个异步处理程序,处理根路径 "/" 的 GET 请求
async fn hello_world() -> &'static str {
    "Hello, World!"  // 返回一个静态字符串作为响应内容
}

use类似于es的import,可以使用三方包或者是标准库代码,使用来use之后,就可以使用这个全局变量(命名空间)了。

需要注意的是:

use axum::{        // 引入 Axum 库
    routing::get,  // 引入用于定义 GET 路由的模块
    Router,        // 引入 Router 类型,用于构建路由器
};

只会“注册”最后一级空间,比如上面的代码,可以使用get,不可以使用routing

Cargo.toml

包的版本请保存一致,可能存在break change

[package]
name = "rust-study"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.5"
tokio = { version = "1", features = ["full"] }

上面代码展示写一个简单http server,写了一个简单的get请求。 每一行都有注释,就不过多解释了。代码书写风格很像nodejs的一些http server 框架。

日志

引入日志(注意版本号和我的保持一致)

cargo add tracing
cargo add tracing-subscriber

Cargo.toml

[package]
name = "rust-study"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
tower-http = { version = "0.5.0", features = ["fs", "trace"] }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"

main.rs

use axum::{ routing::get, Router};
use tower_http::{
    trace::TraceLayer,
};

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let app = Router::new()
        .route("/foo", get(handler))
        .layer(TraceLayer::new_for_http());

    // run it
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    tracing::debug!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

async fn handler() -> &'static str {
    "<h1>Hello, World!</h1>"
}
启动服务(包含日志):

RUST_LOG=trace cargo run

日志级别有:eror、warn、info、debug、trace,级别由左到右从高到低。

get请求

get请求需要处理两种参数,一种是param,一种是query

route path伪代码如下:

path: '/data/detail/:id'

我们请求时: get('/data/detail/123?name=liyunlong&age=40')

id就是123, query就是{name: 'liyunlong', age: '40'}

cargo.toml,加一个序列化的包

serde = { version = "1.0.204", features = ["derive"] }

main.rs

use axum::{ extract::{Query, Path},routing::get, Router};
use tower_http::{
    trace::TraceLayer,
};
use serde::Deserialize;
use axum::response::Html;
use axum::response::IntoResponse;


#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let app = Router::new()
        .route("/foo", get(handler))
        .route("/data/detail/:id", get(query))
        .layer(TraceLayer::new_for_http());

    // run it
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    tracing::debug!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

async fn handler() -> &'static str {
    "<h1>Hello, World!</h1>"
}

#[derive(Debug, Deserialize)]
struct InputParams {
    age: i32,
    name: String,
    job: Option<String>,
}

async fn query(Path(id): Path<i32>,Query(params): Query<InputParams>) -> impl IntoResponse {
    tracing::debug!("id {:?}", id);
    tracing::debug!("query params {:?}", params);
    Html("<h3>Test query</h3>")
}

这样就可以提取 idquery

而且参数类型和InputParams不一致的话,直接帮我们做校验了,不需要额外的配置。

http://localhost:3000/data/detail/123?name=李云龙

Rust入门教学+实战《使用axum实现一个http server》

post请求

Rust入门教学+实战《使用axum实现一个http server》

Rust入门教学+实战《使用axum实现一个http server》

运行: RUST_LOG=trace cargo run

控制台就可以看到body payload解析成功了!。

同时,请求参数和我们struct定义的不一样也会报错的。

Rust入门教学+实战《使用axum实现一个http server》

Rust入门教学+实战《使用axum实现一个http server》 完整代码:

use axum::{ extract::{Query, Path, Json},routing::{get, post}, Router};
use tower_http::{
    trace::TraceLayer,
};
use serde::Deserialize;
use axum::response::Html;
use axum::response::IntoResponse;


#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();

    let app = Router::new()
        .route("/foo", get(handler))
        .route("/data/detail/:id", get(query))
        .route("/json", post(accept_json))
        .layer(TraceLayer::new_for_http());

    // run it
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    tracing::debug!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

async fn handler() -> &'static str {
    "<h1>Hello, World!</h1>"
}

#[derive(Debug, Deserialize)]
struct InputParams {
    age: i32,
    name: String,
    job: Option<String>,
}

async fn query(Path(id): Path<i32>,Query(params): Query<InputParams>) -> impl IntoResponse {
    tracing::debug!("id {:?}", id);
    tracing::debug!("query params {:?}", params);
    Html("<h3>Test query</h3>")
}



#[derive(Deserialize, Debug)]
struct Input {
    name: String,
    email: String,
}
async fn accept_json(Json(input): Json<Input>) -> Html<&'static str> {
    tracing::debug!("json params {:?}", input);
    Html("<h3>Json posted</h3>")
}

连接数据库

通过docker 启动 pg容器:

docker run --name some-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=changeme -e POSTGRES_DB=todolist -p 5432:5432 -d postgres

可以使用navicat等工具连接测试一下。

数据库: todolist
用户名: postgres
密码: changeme

也可以使用免费的工具:dbeaver等。

开发一个todoList

创建表

create table todo (
   id varchar not null,
   description varchar not null,
   completed bool not null);

连接数据库

可以使用bb8bb8_postgres来进行数据库连接与查询操作(也可以选择其他rustORM工具。)

todo list

完整代码参考:(有详细的注释)

github.com/huoguozhang…

使用postman调用结果:

Rust入门教学+实战《使用axum实现一个http server》

docker 构建

Dockerfile

bash
 代码解读
复制代码
# 使用官方的最新Rust镜像作为基础镜像
FROM rust:latest

# 安装构建工具和依赖项
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    build-essential \
    libssl-dev \
    pkg-config \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /usr/src/rust-study

# 将Cargo.toml和Cargo.lock复制到工作目录
COPY Cargo.toml Cargo.lock ./

# 创建空的src目录,以便缓存依赖项
RUN mkdir src

# 预先编译依赖项
RUN cargo build --release
RUN rm -rf src

# 复制项目的所有源文件到工作目录
COPY . .

# 再次构建项目
RUN cargo build --release

# 暴露应用运行的端口
EXPOSE 3000

# 运行构建好的二进制文件
CMD ["./target/release/rust-study"]

执行bash build.sh构建镜像。

注意

  • 构建时需要注意运行时的处理器架构。(建议在目标机器构建)。
  • 有些库linux基础镜像可能不具备,在构建时进行安装。
  • 注意网络问题,能不能拉到docker官方库的镜像、apt-get能不能拉得到,拉不到要想其他方案了。

结语

  • 虽然rust出现的时间不长,但是生态正在蓬勃发展。
  • 因为比较新,国内资料较少,后面技术选型,维护成本也是一个考量因素。
  • 学习rust,“深度优先”找到一个“根结点”,写一个功能的demo,过分纠结语法细节,会一致卡住,特别是rust这种概念多,语法复杂的语言。

参考:github.com/miketang84/…

转载自:https://juejin.cn/post/7389912762049642548
评论
请登录