Rust basics

来自WHY42

使用rust语言编写hello world再容易不过了:

fn main() {
    println!("Hello world!");
}

然后利用rustc编译器编译即可:

rustc hell.rs -o hello.out && ./hello.out

可变(mutable)与不可变(immutable)

rust程序默认的变量是不可变的,类似Scala这种函数式编程的语言,鼓励用户使用immutable的变量。当然如果你非想要使用可变的对象也是支持的:

let i = 32; // immutable
let mut i = 32;

编译器会检查是否对不可变对象重新赋值:

  |
4 |     let i = 10;
  |         -
  |         |
  |         first assignment to `i`
  |         help: make this binding mutable: `mut i`
...
7 |     i = 99;
  |     ^^^^^^ cannot assign twice to immutable variable

那么,对于简单类型直接赋值会有问题,如果是复杂类型,如何呢?比如我们用一个不可变的字符串,然后去调用它的函数改变值,会发生生么情况呢?

--> test.rs:5:5
  |
4 |     let s = String::from("hello");
  |         - help: consider changing this to be mutable: `mut s`
5 |     s.push_str(" world!!!");
  |     ^ cannot borrow as mutable

结果表明,rust依然保持对象是不可变的。看了一下这个方法的定义,有些蹊跷:

pub fn push_str(&mut self, string: &str) {
    self.vec.extend_from_slice(string.as_bytes())
}

具体怎么做到的,我们后面再来研究。

基本类型

rust跟大多数编译型语言一样是静态类型(statically typed)的语言,即所有的变量的类型在程序编译的时候就是已知的。在rust语言中,有着如下的基本类型:

标量类型(Scalar types)

类型 长度


-------- ----------------------------------------

bool 1 true/ false char 4 并不等同于Unicode i8/u8 8 i16/u16 16 i32/u32 32 i32是默认类型,通常拥有最快的速度 i64/u64 64 i128/u128 128 isize/usize arch 取决于机器架构,在32位机器上位32位,64位上位64位 f32 32 浮点数使用IEEE-754标准 f64 64

let f = true;
let sum:i32 = 100;
let heart_eyed_cat = '😻';

复合类型(Compound types)

复合类型分为元组(Tuple)和数组。元组可以用来将不同类型的解构组合到一起:

let t: (i32, bool) = (100, false);

let (x, y) = t; // 解构元组
let x = t.0;    // 或者通过序号访问

数组的与元组的区别在于数组中包含的都是同一种数据类型的值。

let a = [1, 2, 3];
let a: [i32; 5] = [1, 2, 3, 4, 5]; // 显示声明一个数组
let b = [10; 5];                   // 声明初始值为10、长度为5的数组

值得注意的是,在rust中元组和数组都是固定长度的,一旦声明以后就不可以更改。如果非要可变长度的集合,那么可以考虑使用标准库中的`vector`。并且数组中的元素也是不可以更改的,如果尝试去更改一个不可变的对象编译时会出错:

6 |     let b = [100; 5];
  |         - help: consider changing this to be mutable: `mut b`
7 |     b[1] = 1024;
  |     ^^^^^^^^^^^ cannot assign

这和一些其他的语言(例如Java中的final)是有区别的。

数组中如果如果声明的长度和和实际值的长度不一样会怎样呢?rust在编译时就会出错:

 --> hell.rs:11:23
   |
11 |     let a: [i32; 3] = [1];
   |                       ^^^ expected an array with a fixed size of 3 elements, found one with 1 element

另外,rust程序会在运行时对数组的边界进行检查,如果越界访问数组将抛出错误而结束程序,而不是返回一个错误的内存。

方法

在rust中定义一个方法使用`fn`关键字定义:

fn foo(i: i32, j: i32) {
    let sum = i + j
}

// 带有返回值的方法
fn sum(i: i32, j: i32) -> i32 {
    i + j
}

在rust中方法是第一类值,意味着你可以这样操作:

let fn_s  = sum;
let s = fn_s(i, j);

另外,方法中包含在大括号中的语句块,被称作是表达式(expression),可以这样用:

let a = {
   e + 10
};
println!("{}", a);

流程控制

if语句

rust的if语句和其他语言基本类似,稍微有一点区别:

if e % 2 == 0 {        // if条件后面不用写小括号
    println!("{}", e);
} else if e % 3 == 0 { // 但是后面的语句块必须包含在大括号之中,哪怕只有一行
    println!("{} % 3 ==0", e);
} else {
    println!(":p");
}

条件赋值

因为if语句本身是一个表达式,所以可以把if和let联合在一起来使用,也就是条件赋值:

let a = if condition {
    5
} else {
    6
};

当然前提是不同的分支下的语句要是一样的类型,否则编译器会检测出错误。

循环

rust的`loop`关键字支持创建一个循环:

let mut i = 0;
loop {
    i += 1;
    println!("->{}", i);
}

基本上这就是一个死循环了。不知道为啥要定义这样一个奇葩的关键字。索性我们可以像其他编程语言一样`break`。值得注意的是,跟条件赋值一样,loop语句也是可以和let一起来赋值的,像下面这样:

let s = loop {
    i += 1;
    println!("->{}", i);
    if(i > 100) {
        break i;
    }
};
println!("s = {}", s); // s = 101

除了这个`loop`外,也可以“正常的”像其他语言一样,使用`while`和`for`进行条件循环:

while i < 1000 {    // 不用写小括号
    i += 1;
}

for e in a.iter() { // 使用for循环遍历数组
    println!("{}", e); 
}

// for i in (1..10).rev()
// 使用rev()反转顺序
for i in (1..10) {
    println!("{}", i);
}

rust语言的一些惯例

命名方式

rust中推荐使用蛇形命名(snake case)来作为方法和变量的命名方式,所有的标识符都是小写且使用下划线分隔,例如:

let foo_bar = 1;

fn print_info() {

}