从C、C++的视角来看Rust

本篇文章用于有C、C++基础的读者快速入门Rust语言。

一、工具链

编译工具 gcc/clang、g++/clang++ -> rustc

构建系统 make/cmake/Bazel -> cargo cargo用于管理和辅助创建一个rust项目,它通过Toml配置文件进行项目管理。 它具备将上一次构建成功的状态记录到Cargo.lock的功能,这对于第三方crate(类似库)的版本管理非常有帮助,这意味着你在发布的每个版本只要存在Cargo.lock,就可以按照当时构建成功的配置进行构建(反例:Gradle、Maven和JDK、SDK、NDK甚至cmake等工具链存在不兼容的情况)。

编辑器增强 clangd/IntelliSense -> rust-analyzer 用于提供IDE、编辑器对RUST语言的支持能力,提供代码补全、跳转、重构等能力。

二、项目结构

一个Rust项目称为一个Projects,它包含package、crate、module结构。 一个package包含多个crate、但是只能有一个library crate(可以有无数多个binary crate)。 一个crate可以是library crate(特点包含lib.rs),也可以是binary crate(包含函数入口),也可以两者都是。它可以实现多个module。

三、基本语法

3.1 数据类型及定义方式

3.1.1 基本数据类型

其中C/C++的long、long int、long double、wchar_t类型存储的数据长度和操作系统及其位宽相关。

  • Rust如果通过debug构建,默认带有溢出检查,当发生数值溢出后会panic终止程序。
    • 如果希望在release版本也启用检查,需要overflow-checks = true配置。
  • Rust程序中,一旦发生数组越界,就会触发panic终止程序。
Length Signed Unsigned C/C++ Signed C/C++ Unsigned
8-bit i8 u8 char/signed char unsigned char
16-bit i16 u16 short int/signed short int unsigned short int
32-bit i32 u32 short int/signed int unsigned int
64-bit i64 u64 long/long int/signed long int/long long unsigned long
128-bit i128 u128
arch isize usize
32-bit f32 float
64-bit f64 double
128-bit long double
32-bit char wchar_t
8-bit bool bool

Rust默认整型数据类型为i32,默认浮点数数据类型为f64。

rust 复制代码
// 可以在定义数据类型的时候不指定数据类型(此处默认是i32类型)
let a = 32;
// 也可以指定其对应的数据类型
let a: i64 = 32;
let z: char = 'ℤ';

3.1.2 复合数据类型

3.1.2.1 元组(Tuple)类型

该数据类型是Rust特有,C/C++不具备的数据类型。 元组可以将多种不同的数据类型组合形成一个复合结构,一旦定义后就无法再增长或缩减。

rust 复制代码
// 可以让编译器自动推断数据类型
let tup = (500, 6.4, 1);
// 也可以自己写明数据类型
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 拆解元组的方式
let (x, y, z) = tup;
// 元组的使用方式
let five_hundred = x.0;

3.1.2.2 数组(Array)类型

数字可以存储一系列同数据类型的数据,定义后具有固定的长度。

rust 复制代码
// 定义和初始化
let a = [1, 2, 3, 4, 5];
// 指定数据类型和长度,并初始化
let a: [i32; 5] = [1, 2, 3, 4, 5];
// 初始化缩写写法,相当于let a = [3, 3, 3, 3, 3];
let a = [3; 5];

下面是C/C++定义数组的方式

C++ 复制代码
int a[5] = {1, 2, 3, 4, 5};
// 动态数组定义,需要通过delete进行删除
int *a = new int[100];
delete []a;
// 初始化前两个元素,其他元素全部默认为0
int *a = new int[100]{ 3, 5 };
// 初始化所有元素为0,但是并不是所有编译器都支持
int a[5] = { 0 };

3.2 宏定义

rust的宏和C/C++的宏一样,都是在编译前进行展开和替换,然后再进行编译步骤。 #define -> macro_rules!

C/C++的宏定义写法如下,定义一个字符串字面量

C 复制代码
#define TASK_STATUS_TEMPLATE "/proc/{}/status"

{
	printf("%s\n", TASK_STATUS_TEMPLATE);
}

Rust的写法如下,定义一个字符串字面量

rust 复制代码
macro_rules! TASK_STATUS_TEMPLATE { () => { "/proc/{}/status" } }

{
	println!("{}", TASK_STATUS_TEMPLATE!());
}

我们可以注意到,println!也是一个内置宏,它会在编译前进行展开,转换成方法调用。

rust 复制代码
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "print_macro")]
#[allow_internal_unstable(print_internals)]
macro_rules! print {
    ($($arg:tt)*) => {{
        $crate::io::_print($crate::format_args!($($arg)*));
    }};
}

#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "println_macro")]
#[allow_internal_unstable(print_internals, format_args_nl)]
macro_rules! println {
    () => {
        $crate::print!("\n")
    };
    ($($arg:tt)*) => {{
        $crate::io::_print($crate::format_args_nl!($($arg)*));
    }};
}

3.3 方法定义

rust的入口函数默认是main。 通过std::env::args()可以获取到程序传入参数,通过skip函数跳过了第一个路径参数。

  • Rust定义函数的关键词是fn
  • Rust的函数定义可以在任何位置,不必像C/C++一样,调用之前,必须先声明函数。
rust 复制代码
fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    // {:?}可以将args的信息详细输出,以debug的格式
    println!("{:?}", args);

    another_function();
    print_labeled_measurement(five(), 'h');
}

fn another_function() {
    println!("Another function.");
}

// 定义方法的参数,必须有明确的数据类型
fn print_labeled_measurement(value: i32, unit_label: char) {
	// rust格式化输出有两种方式,一种是{value}这样直接使用变量名
	//                       另一种是("{}", value)这种方式,其中{}中可以添加格式化的方式
	//                       比如{:.3},它的作用是输出浮点数时保留小数点后三位
    println!("The measurement is: {value}{unit_label}");
}

// 定义方法的返回参数,也必须有明确的数据类型
// 返回值类型写在->后面
// 这里可以发现,5后面没有`;`,在rust中,最后一条语句没有分号,那么它的值将会被返回
// 也可以通过return 5;返回
fn five() -> i32 {
    5
}

C/C++的方式

c++ 复制代码
void another_function() {
	printf("Another function.\n");
}

int five() {
	return 5;
}

void print_labeled_measurement(int value, char unit_label) {
    printf("The measurement is: %d%c\n", value, unit_label);
}

int main(int argc, char *argv[]) {
	for(int i = 0; i < argc; ++i) {
        std::cout << "Argument [" << i << "] is " << argv[i] << std::endl;
    }
    another_function();
    print_labeled_measurement(five(), 'h');
}

3.4 流程控制

  • 需要注意,rust的流程控制条件必须是bool类型,其他数据类型均会编译出错。

3.4.1 if

和C/C++的使用方式类似,但是rust有其特有的方式,if - else也可以返回数据,并且赋值给变量。 if - else的返回值必须是同数据类型,否则无法通过编译。

下面是rust的方式

rust 复制代码
let n = if condition { 5 } else { 6 };

if number < 5 {
	println!("condition was true");
} else if number % 3 != 0 {
	println!("condition was false");
}

下面是C/C++的方式

c 复制代码
// 这种使用方式,rust不支持
int n = condition ? 5 : 6;

if (number < 5) {
	printf("condition was true\n");
} else if (number % 3) {
	printf("condition was false\n");
}

3.4.2 loop

Rust具备其特有的循环关键词loop,loop也可以返回值赋值给变量,也可以进行跳转。

rust 复制代码
loop {
	println!("again!");
}

// 在循环中满足状态后返回,并将值传递给变量
let result = loop {
	counter += 1;

	if counter == 10 {
		break counter * 2;
	}
};

// 在循环中,还可以使用类似C/C++ goto的语法
// 在break的时候,可以跳到对应的label位置。
'counting_up: loop {
	println!("count = {count}");
	let mut remaining = 10;

	loop {
		println!("remaining = {remaining}");
		if remaining == 9 {
			break;
		}
		if count == 2 {
			break 'counting_up;
		}
		remaining -= 1;
	}

	count += 1;
}

C/C++的方式

C 复制代码
while (true) {
	printf("again!\n");
}

int result = 0;
for (;;) {
	counter += 1;

	if (counter == 10) {
		result = counter * 2;
		break;
	}
}

counting_up:
while (1) {
	printf("count = %d\n", count);
	int remaining = 10;

	for (;;) {
		printf("remaining = %d\n", remaining);
		if (remaining == 9) {
			break;
		}
		if (count == 2) {
			goto counting_up;
		}
		remaining -= 1;
	}

	count += 1;
}

3.4.3 while

rust的写法。rust禁止使用number--的语法。

rust 复制代码
while number != 0 {
	println!("{number}!");

	number -= 1;
}

C/C++的写法

C++ 复制代码
while (number) {
	printf("%d!\n", number);

	number--;
}

3.4.4 for

Rust的for可以遍历数组、vec等数据结构。

rust 复制代码
let a = [10, 20, 30, 40, 50];

for element in a {
	println!("the value is: {element}");
}


for number in (1..4).rev() {
	println!("{number}!");
}
println!("LIFTOFF!!!");

C/C++的写法:

C++ 复制代码
int a[] = {10, 20, 30, 40, 50};

for (auto element : a) {
	printf("the value is: %d\n", element);
}

for (size_t number = 3; number >= 1; number--) {
	printf("%d!\n", number);
}
printf("LIFTOFF!!!\n");

3.5 结构体 - struct

Rust的结构体,可以存储一系列数据结构,类似于C++中的struct(默认权限public)或class(默认权限private)。

下面是定义一个用于描述矩形的struct

rust 复制代码
// 加入#[derive(Debug)]之后的struct,可以通过{:?}输出debug信息。
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

// 此处表明我们需要为Rectangle结构体添加一个新方法
// 可以多处进行impl进行添加,无需放在一起
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}


fn main() {
    let scale = 2;
    // struct在初始化后,默认存放在栈中。
    let rect1 = Rectangle {
	    // dbg宏使用后,会在终端输出相关变量的调试信息,不会中断程序执行
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
    println!( "The area of the rectangle is {} square pixels.", rect1.area() );
}

以下是C++的第一种写法。

C++ 复制代码
class Rectangle {
public:
    unsigned int width;
    unsigned int height;

    Rectangle(unsigned int w, unsigned int h) : width(w), height(h) {};

    unsigned int area() const {
        return width * height;
    }

    bool can_hold(const Rectangle& other) const {
        return width > other.width && height > other.height;
    }
};

int main() {
    int scale = 2;
    Rectangle rect1(30 * scale, 50);

    std::cout << "width: " << rect1.width << ", height: " << rect1.height << std::endl;
    std::cout << "The area of the rectangle is " << rect1.area() << " square pixels.\n";

    return 0;
}

以下是C++的第二种写法

C++ 复制代码
struct Rectangle {
    unsigned int width;
    unsigned int height;

    // 构造函数
    Rectangle(unsigned int w, unsigned int h) : width(w), height(h) {}

    // 计算面积的成员函数
    unsigned int area() const {
        return width * height;
    }

    // 判断是否可以容纳另一个Rectangle的成员函数
    bool can_hold(const Rectangle& other) const {
        return width > other.width && height > other.height;
    }
};

int main() {
    int scale = 2;
    Rectangle rect1(30 * scale, 50);
    
    std::cout << "width: " << rect1.width << ", height: " << rect1.height << std::endl;
    std::cout << "The area of the rectangle is " << rect1.area() << " square pixels.\n";

    return 0;
}

3.6 枚举 - enum

Rust的枚举类型和struct一样,也可以拥有自己的方法,并且还可以存储不同的数据类型。 同时可以通过match关键词,来对枚举数据类型进行自动匹配。 而C++不行。

Rust 复制代码
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn describe(&self) {
        match self {
            Message::Quit => {
                println!("Quit");
            }
            // 结构体需要解构
            Message::Move {x, y} => {
                println!("Move to {}, {}", x, y);
            }
            // 其他成员可以直接匹配
            Message::Write(text) => {
                println!("Text message: {}", text);
            }
            // `_`表示其他类型
            _ => {
	            println!("Other");
            }
        }
    }
}

// Rust常用的Option和Result也是枚举类型
// 此处的T代表范型
enum Option<T> {
    None,
    Some(T),
}

pub enum Result<T, E> {
   Ok(T),
   Err(E),
}

C/C++的枚举类型,默认都是整形。

C++ 复制代码
enum MyEnum {
	A = 1,
	B = 2,
	// C会自动沿用前一个枚举类型的值+1
	C,
	D = 10
};

3.7 trait

Rust中的trait代表的是一类相同的行为,类似于"接口"。 一个trait可以定义一组方法,并且可以具有默认实现,这些方法可以在任何数据类型上实现。 我们可以为任意数据结构实现一个trait包含的方法,该方法会替代默认实现被使用。

Rust通过trait实现类似C++运算符重载的机制(但是Rust本身没有重载机制)。

DisplayCopyPartialOrd等是Rust标准库中定义的一些trait,在Rust中有特殊的含义和作用。

  • Display:这个trait提供了一个方式来格式化输出到用户的字符串。例如,当你想打印一些值或是把这个值转換成一个字符串来显示给用户,就必须实现Display trait。

  • Copy:这个trait表示一种类型的值可以进行"堆栈复制",即这个类型的值可以用简单的按位复制来製作新的实例,并且這並不會使原值失效。所以一旦我们使用了这个值,它就会产生一份拷贝。原生类型(例如i32、f64等)都是可复制的,自定义类型需要显式实现Copy trait。

  • PartialOrd:这个trait用于比较两个值的大小。它定义了一个partial_cmp方法,比较自身和另一个同类型的值,返回一个Option<Ordering>实例。这个实例可以是Some(Less)Some(Equal)Some(Greater),也可以是None(表示两个值不能进行比较)。大多数类型都实现了PartialOrd trait,以便于我们进行比较操作。例如,所有整型和浮点型都实现了这个trait。

  • Debug:这个 trait 用于支持调试输出,它提供了一个格式化输出至开发者的字符串。如果你使用 {:?}{:#?} 进行输出,那你的类型就需要实现 Debug trait。

  • EqPartialEqPartialEq trait 用于比较两个类型的各个实例是否相等(使用 ==!= 符号)。可以看作一种 relaxed 的等价观念,因为它也可以用于 float 类型,比如 NaN 是不等于 NaN 的。而 Eq trait 则表示一种更强的"等价"观念,如果 a == b 和 b == a 总是相等,那一般就实现它,它是一个"空"(marker)trait,表示满足某种性质。

  • Ord:这个 trait 是 PartialOrd 的超集,用于完全排序的类型,它通过 cmp 方法返回一个 Ordering(而不是 Option<Ordering>)。也就是说,对于实现了 Ord 的类型的任意两个值,我们总是可以确定一个明确的排序关系。

  • Default: 用于创建一个类型的默认值。

  • Clone: 这个trait表示一个对象可以准确复制一份出来,和 Copy 不同之处在于,当 Copy 发生时原始值仍然有效,而 Clone 通常和所有权、生命周期有关。显式调用 clone() 方法可以获取类型的一个全新对象,这通常发生在复杂类型,比如 VecString 或者拥有 heap 内存空间等。

  • Deref and DerefMut:这两个trait用于重载解引用运算符(*),分别用于不可变和可变的引用。

  • 除此之外还有很多其他的trait可以用,比如实现std::ops::Add将可以覆盖+的行为。

这些trait的存在使得Rust的类型系统更加灵活,我们可以通过实现不同的trait对自定义类型进行定制,使得这些类型具有更丰富的功能。

rust 复制代码
use std::fmt::Display;

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

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

// 可以为一个数据结构,同时实现多个trait,此处没有进行实现。
// 此处是通过trait进行限制,要求范型T实现了Display和PartialOrd这两个trait,才允许执行cmp_display函数
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

四、所有权和生命周期

Rust和其他语言很多不同之处,其中最大最重要的一点就是其所有权机制和生命周期的概念。

在Rust中,堆上的数据,都有指向其的指针存储在栈上,而指针没有实现Copy trait,因此赋值是move语义。一旦指针的生命周期结束,存放在堆上的数据将会被自动释放。

4.1 可变性

在Rust语言中,所有的变量默认都是不可变的。 如果想在后面对其进行变更,就需要mut这个关键词修饰。

基本数据类型,都实现了Copy这个trait,所以当发生赋值的时候,所有权并不会转移,而是创建一个新的变量并赋值
如果数组存放的数据类型是实现了Copy trait的,那么它的再赋值也是拷贝,而不是移动。

rust 复制代码
// 不可变变量在后面无法再被赋值,发生赋值行为会导致编译检查失败
let apples = 5; // immutable
// 可变变量可以在后面重新赋值
let mut bananas = 5; // mutable
// 此时apples依旧可以被使用,它并未发生所有权转移。
let oranges = apples;

let a = [0; 100];
// 此时数组a依旧可以被使用,因为其存储元素实现了Copy trait,因此赋值给b是创建新的数组并复制了一份。
let b = a;

4.2 堆和栈的使用

上面提到了实现了Copy trait的数据类型数组赋值的时候,会创建一个新的数组,并进行数据拷贝。 数组默认是存储在栈上的。 但是有些情况下(比如减少内存使用),我们并不希望它进行数据拷贝,而是进行转移(move)。 在这种情况下,我们可以将数据存储到堆上。 在Rust中,所有在堆上的数据,都有指向其的指针存储在栈上,而指针没有实现Copy trait,因此赋值是move语义。 一旦指针的生命周期结束,存放在堆上的数据将会被自动释放。

我们可以通过Box将数组分配到堆中。

C/C++中自身不具备自动释放堆上内存的能力,但是可以通过标准库中的智能指针实现(std::unique_ptrstd::shared_ptr)。

4.3 遮蔽(Shadowing)

遮蔽可以让一个同名变量重复被使用。

rust 复制代码
fn main() {
    let x = 5;
	// 此处赋值后,上面的x将会失效
    let x = x + 1;

    {
        // 此处x在离开花括号后失效
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}

C/C++也有遮蔽的功能,但是不允许在同一个作用域中进行遮蔽。 下面这种行为是不允许的。

C++ 复制代码
int main() {
	int x = 5;
	// 此处会编译报错
	int x = x + 1;
}

4.4 引用和借用(References and Borrowing)

Rust在进行方法调用时,也会发生所有权的转移,比如下面这段代码。

rust 复制代码
fn main() {
    let m1 = String::from("Hello");
    let m2 = String::from("world");
    greet(m1, m2);
    let s = format!("{} {}", m1, m2); // Error: m1 and m2 are moved
}

fn greet(g1: String, g2: String) {
    println!("{} {}!", g1, g2);
}

当后面像通过format宏进行格式化的时候,m1和m2的所有权已经在转移到了greet函数内。其生命周期也因此被重新限制在了greet函数内,并在执行完成后被释放了。

如果我们想要m1、m2对应的堆数据不被释放,那么我们需要在函数执行结束后将所有权再转移回去。

rust 复制代码
fn main() {
    let m1 = String::from("Hello");
    let m2 = String::from("world");
    let (m1_again, m2_again) = greet(m1, m2);
    let s = format!("{} {}", m1_again, m2_again);
}

fn greet(g1: String, g2: String) -> (String, String) {
    println!("{} {}!", g1, g2);
    (g1, g2)
}

这种方式无法保证传入变量的生命周期,不受到方法影响。 因此我们能否将变量的值借用给方法,而不将所有权传递给方法呢? 这里就可以用到Rust的引用了。

rust 复制代码
fn main() {
    let m1 = String::from("Hello");
    let m2 = String::from("world");
    greet(&m1, &m2); // note the ampersands
    let s = format!("{} {}", m1, m2);
}

fn greet(g1: &String, g2: &String) { // note the ampersands
    println!("{} {}!", g1, g2);
}

&m1 这样的语法,就是创建一个m1的引用(或借用),我们可以看到greet方法对形参中借用的声明是&String,关键是&

C/C++中可以通过传递指针或传递引用的方式给一个函数。 指针和引用的区别只有几点

  • 指针赋值后,可以被修改,但是引用一旦赋值,就无法再被修改了。
  • 引用不能是空值。
  • 指针可以算数运算,而引用不行。 除此之外,使用方式区别不大。

4.5 解引用指针访问堆上数据

和C++一样,可以通过*对一个指针解引用,访问其堆上数据。

rust 复制代码
let mut x: Box<i32> = Box::new(1);
let a: i32 = *x;         // *x 读取堆上的值, 因此 a = 1
*x += 1;                 // *x 通过x直接修改堆上的数值,此时x指向的内存数据变成2

let r1: &Box<i32> = &x;  // r1 在栈上指向x
let b: i32 = **r1;       // 我们对r1进行两次解引用,才可以访问到堆上内存

let r2: &i32 = &*x;      // r2 直接指向堆上内存
let c: i32 = *r2;        // 因此r2进行单次解引用就可以访问堆上的内存

4.6 借用引起访问权限变化

Rust中,借用检查器的核心思想是变量存在三种权限。

  • Read (R): 数据可以被拷贝到其他位置
  • Write (W): 数据可以被直接修改
  • Own (O): 数据可以被移动或丢弃 默认情况下,Rust中的变量默认具有RO权限,如果添加了mut关键词,将会具备RWO属性。

4.6.1 非可变借用

  • 第一行v复制后,获得RWO权限
  • 第二行v中的数据被num借用,此时v将失去WO权限,它后面只能被读取。
    • 变量num获取到RO权限,但是num不可写,因为没有mut关键词修饰。
  • 第三行*num在println宏使用完之后,后续没有再使用到num的地方了,因此num的借用权限被取消,同时num重新获得WO权限。
  • 第四行执行结束后,v变量不再被使用了,因此它将会被丢弃,并且失去所有的权限。

4.6.2 可变借用

和不可变借用不同的是,当使用可变借用之后,被借用数据结构将失去所有权限。在借用使用结束之前,都无法被读写。

4.7 切片(Slice)

rust 复制代码
let s = String::from("hello world");

let hello: &str = &s[0..5];
let world: &str = &s[6..11];
let s2: &String = &s; 

切片后的权限变化。

五、常用Collections

5.1 Vec

类似动态数组

rust 复制代码
    let v: Vec<i32> = Vec::new();
    let v = vec![1, 2, 3];

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);

    for n_ref in &v {
        // n_ref has type &i32
        let n_plus_one: i32 = *n_ref + 1;
        println!("{n_plus_one}");
    }

5.2 String

字符串类型,string默认存储的是UTF-8类型

rust 复制代码
    let mut s = String::new();

    let data = "initial contents";

    let s = data.to_string();

    // the method also works on a literal directly:
    let s = "initial contents".to_string();
    let s = String::from("initial contents");

5.3 HashMap

存储键值对的方式。

rust 复制代码
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

六、智能指针

  • Box<T>: 分配数据在堆上
  • Rc<T>: 引用技术类型的指针,允许多个所有权
  • Ref<T>RefMut<T>,或者RefCell<T>

七、示例项目

linux: github.com/luoqiangwei...

android: github.com/luoqiangwei...

参考链接

rust-book.cs.brown.edu/experiment-...

相关推荐
QMCY_jason4 小时前
Ubuntu 安装RUST
linux·ubuntu·rust
碳苯8 小时前
【rCore OS 开源操作系统】Rust 枚举与模式匹配
开发语言·人工智能·后端·rust·操作系统·os
zaim110 小时前
计算机的错误计算(一百一十四)
java·c++·python·rust·go·c·多项式
凌云行者19 小时前
使用rust写一个Web服务器——单线程版本
服务器·前端·rust
cyz1410011 天前
vue3+vite@4+ts+elementplus创建项目详解
开发语言·后端·rust
超人不怕冷1 天前
[rust]多线程通信之通道
rust
逢生博客1 天前
Rust 语言开发 ESP32C3 并在 Wokwi 电子模拟器上运行(esp-hal 非标准库、LCD1602、I2C)
开发语言·后端·嵌入式硬件·rust
Maer091 天前
WSL (Linux)配置 Rust 开发调试环境
linux·运维·rust
白总Server1 天前
CNN+Transformer在自然语言处理中的具体应用
人工智能·神经网络·缓存·自然语言处理·rust·cnn·transformer
凌云行者1 天前
使用rust写一个Web服务器——async-std版本
服务器·前端·rust