Rust入门手记

Rust

本篇文章记录一下Rust的入门学习过程,主要是一些基础的语法和一些常用的库的使用,以及一些常见的问题的解决方法。其实本来就想开始入门了,但是这学期教授们不知道怎么的,不约而同把作业量提到了往年的两倍,所以春假才有时间搞这个。

长期规划

因为今年实习被分配写rust,所以打算下一个项目用rust写一个kv数据库,类似于Leveldb那样。模仿一下前人的GitHub。但是在此之前,得先了解rust的基本语法和一些常用的知识。

基础语法

这个给我的感觉就是C++,golang和Ocaml揉碎了成一团之后的产物。当我看到let,match和option的时候我就知道为啥我被组长挑来写rust了哈哈哈。但是其中还是有很多imperative language feature的。虽然我目前还是不是很能体会为什么很多设计者试图在rust中追求functional language的feature,但是这种设计确实将rust的入门门槛和可用性降低了很多。我还记得当时有人评价Ocaml说,Once it compiles, it will never crash。在写代码的时候严格约束确实是一件好事。

基本语法

变量

首先所有的变量分为可变和不可变两种,可以和Ocaml一样用let声明,但是不同的是,rust的let是一个表达式,而不是一个语句。所以可以这样写:

let x = 5;
let y = {
    let x_squared = x * x;
    x_squared * x_squared
};

这句话的等价于:

int x = 5;
int x_squared = x * x;
int y = x_squared * x_squared;

和Ocaml的

let x = 5 in
let x_squared = x * x in
let y = x_squared * x_squared in
y

这种写法是一样的。这种写法的好处是可以在let中定义一些临时变量,而不用担心变量名的冲突,非常方便。

可变vs不可变

rust中的变量分为可变和不可变两种,不可变的变量在定义之后就不能再次赋值,而可变的变量可以在任何地方被赋值。默认情况下,变量是不可变的,如果想要定义一个可变的变量,需要在let前面加上mut关键字。比如:

let mut x = 5;
x = 6; // OK
let y = 5;
y = 6; // Error

数据类型

跳过基础类型,我们来看复合类型吧。

首先是arr和vec,这两个都是数组,但是vec是可变的,arr是在编译期确定的大小,有点类似于std::array和std::vector,但是vec的实现是unsafe的,但是显然也没法被低成本替换。

let a = [1, 2, 3, 4, 5];
let mut v = vec![1, 2, 3, 4, 5];
v.push(6); // OK
println!("{:?}", v); //1 2 3 4 5 6
println!("{:?}", a); //1 2 3 4 5

那么我们怎么访问数组中的元素呢?因为rust是strongly typed,所以我们需要指定下标中元素的类型。

在cpp中,我们可以使用int作为subscript,但是这里可能要改一下

int a[30];
for (int i = 0; i < 30; i++) {
    a[i] = i;
}

在rust中,我们需要使用usize作为subscript,这个类型是一个unsigned int,所以我们这样写会哟对岸问题

let mut a = [0,1,2,3,4,5];
for i in 0..3 {
    v[i] = i; //error
}

这里会报错,因为usize是一个unsigned int,但是for循环中的i是一个signed int,所以会报错。所以我们需要这样写:

for i in 0..3 {
    v[i as usize] = i; //OK
}

虽然没cpp自由,但是在大型项目中,这种类型检查还是很有必要的。把时间花在写起来能跑的代码上总比花时间在debug上好。

如果我们需要对一个array的某一个范围进行排序,我们可以这样写:

let mut a = [5, 4, 3, 2, 1];
a[1..4].sort();
println!("{:?}", a); //5 2 3 4 1

或者获取mutable slice

let mut a = [5, 4, 3, 2, 1];
let b = &mut a[1..4];
b.sort();
println!("{:?}", a); //5 2 3 4 1

我们可以自定义比较符号

let mut a = [5, 4, 3, 2, 1];
a[1..4].sort_by(|a, b| b.cmp(a));
println!("{:?}", a); //5 4 3 2 1

或者自定义比较函数

let mut a = [5, 4, 3, 2, 1];
a[1..4].sort_by(|a, b| b.partial_cmp(a).unwrap());
println!("{:?}", a); //5 4 3 2 1

Match关键字

match关键字是rust中的一个非常重要的关键字,它可以用来匹配一个值的类型,然后执行相应的代码。比如:

这个和Ocaml里面的match关键字是一样的,但是rust的match更加高级,因为加入了一些imperative language的内容,所以肯定有所改变

常用的match就不说了,比如高级if

fn work(x: i32) -> Option<i32> {
    if x < 0 {
        None
    } else {
        Some(x)
    }
}

fn main() {
    let x = 5;
    match work(x) {
        Some(y) => println!("x is {}", y),
        None => println!("x is negative"),
    }
}

range match

fn main() {
    let x = 5;
    match x {
        1 => println!("one"),
        2 | 3 | 5 | 7 | 11 => println!("x is a prime"),
        13..=19 => println!("x is a teen"),
        _ => println!("x is something else"),
    }
}

然后就是function match

fn test() -> i32 {
    5
}

fn main() {
    match test() {
        0 => println!("zero"),
        n @ 1..=12 => println!("n is {}", n),
        n @ 13..=19 => println!("n is a teen"),
        _ => println!("n is something else"),
    }
}

这里的n是把test()这么一个暂时的结果绑定到了n上,使得我们可以在后面的代码中使用n。

Struct

rust的struct就是golang和cpp中的struct,但是rust中的struct是可以有方法的。但是不得不说,这点很像golang。在golang里面,我们一般是有一个类的interface,然后会有一个XXImpl类去实现抽象方法和多态,但是在rust里面,这么多并不是选择题,而是必须的。比如我定义一个foo的struct,那么我就必须要实现一个foo的方法,否则就会报错。这样的好处是,我们可以在编译期就发现一些错误,而不是在运行期。

struct Foo {
    x: i32,
}

impl Foo {
    fn new(x: i32) -> Foo {
        Foo { x }
    }

    fn foo(&self) {
        println!("x is {}", self.x);
    }
}

fn main() {
    let f = Foo::new(5);
    f.foo(); //5
}

这点不得不说真的非常像golang,也不知道是golang借鉴了rust还是rust借鉴了golang。

然后我们可以来写一道简单的算法题,比如最大子段和

fn main() {
    let cin = stdin();
    let mut line = String::new();
    cin.lock().read_line(&mut line).unwrap();
    let n: i32 = line.trim().parse().unwrap();
    cin.lock().read_line(&mut line).unwrap();
    let mut a:Vec<i32> = line.trim().split_whitespace().map(|x| x.parse().unwrap()).collect();
    let mut ans:i32 = 0;
    let mut cur:i32 = a[0];
    let mut prev:i32 = 0;
    for i in 1..n {
        cur = max(prev, 0) + a[i as usize];
        ans = max(ans, cur);
        prev = cur;
    }
    println!("{}", ans);

}

这个我们实际上是要用buffered reader来读取,但是这里为了方便,就直接用了cin了。

Trait

Trait是rust中的一个非常重要的概念,它是一种抽象的接口,比如我们可以定义一个trait,然后让struct去实现这个trait,这样的话,我们就可以在函数中传入这个trait,而不是具体的struct,这样的话,我们就可以在函数中使用这个trait的方法,而不是具体的struct的方法。这样的话,我们就可以在函数中使用多态了。

trait Foo {
    fn foo(&self);
}

struct Bar {
    x: i32,
}

impl Foo for Bar {
    fn foo(&self) {
        println!("x is {}", self.x);
    }
}

fn test<T: Foo>(t: T) {
    t.foo();
}

fn main() {
    let b = Bar { x: 5 };
    test(b);
}

这是一种非常强大的工具。我们具有了C++ programmer所有的工具和能力,去构造更多的,更高效的程序。

posted @ 2023-03-16 08:39  tiany7  阅读(90)  评论(0编辑  收藏  举报