rust从零单排

161 阅读8分钟

同步环境

os: ubuntu20.04
rust: rustc 1.59.0

注释

# 单行注释,可以出现在文本的任意地方
// 
/* 注释 */

# 文档注释,可以出现在函数体外或者主函数,在函数中会报错
///

# 多行注释,函数体内和函数体外
/*
*
*注释 
*/

输出

fn main(){
    let a = 5;
    println!("{}",a);
    
    //主函数的最后不能是表达式,但是函数的最后会把表达式的值返回,当做函数的返回值
    5
}
// 函数可以在任何的地方声明,函数的返回值必须显示的声明,不然默认没有返回值是一个纯过程的函数,如果有返回值,函数体的最后执行语句如果是一个表达式,将把表达式的值作为函数的返回值,只能在最后才可以,一般我们都显示的使用return 关键字返回函数的返回值
fn add(a:i32,b:i32)->i32{
    5
}

if

fn main(){
    let a = 5;
    // 可以看到if后面的条件可以不使用(),但是也可以使用
    // 不支持 单执行语句省略{}括号的做法
    if a == 4 {
        ...
    } else if a == 5 {
        ...
    } else {
        ...
    }
}

while

tips: do 是保留的关键字,但是没有do + while 的语法,也许后期会扩展

fn main(){
    // 注意,如果while 的判断条件需要变化,声明变量的时候需要声明为可变,要不然无法赋值
    let mut a = 1;
    while a != 4 {
        a = a +1;
    }
    println!("OVER");
}

for

fn main(){
    // 数组
    let a = [1,3,5,7,9];
    // a.iter() 倆种迭代方式不一样,后面我们慢慢详解
    for i in a {
        // i 不用声明,类型自动的推导
        println!("{}",i)
    }
    // 不支持
    for{
    
    } 
    // 不支持
    for a == 1 {
    } 
}

loop

// 场景一
fn main(){
    let a = 1;
    // loop 就是传说中的死循环,rust原生支持
    loop{
        if a == 1 {
            // 跳出循环
            break
        }
    }
    
}

// 场景二
fn main() { 
    let s = ['R''U''N''O''O''B']; 
    let mut i = 0// 把内部循环的结果返回,想想在一个链表种查找数据的时候,找到了是不是应该把数据返回给调用的地方,当然这只是举例,
    let location = loop { 
        let ch = s[i];
        if ch == 'O' { 
            break i; 
        } 
        i += 1; 
    }; 
    println!(" \'O\' 的索引为 {}", location); 
}

所有权

所有权是rust的内存高效管理方式,c/c++手动申请并释放,安全隐患大;java使用jvm自动的,但是它的gc机制会使程序占用较大的内存资源。

  • Rust 中的每个值都有一个变量,称为其所有者。
  • 一次只能有一个所有者。
  • 当所有者不在程序运行范围时,该值将被删除。
    1. 出了变量的作用域,生命周期结束,系统会自动的调用drop()函数,释放内存
    {
        // 在声明以前,变量 s 无效
        let s = "runoob";
        // 这里是变量 s 的可用范围
    }
    // 变量范围已经结束,变量 s 无效
    

移动

  • 数据的传递
let a = 5;
let b = a;
  1. 将5 赋值给a变量
  2. 将a 的值再赋值给b
  3. 此时栈中有两个变量,值都为5

此情况中的数据是"基本数据"类型的数据,不需要存储到堆中,仅在栈中的数据的"移动"方式是直接复制。

"基本数据"类型有这些:

  • 所有整数类型,例如 i32 、 u32 、 i64 等。
  • 布尔类型 bool,值为 true 或 false 。
  • 所有浮点类型,f32 和 f64。
  • 字符类型 char。
  • 仅包含以上类型数据的元组(Tuples)。

但如果发生交互的数据在堆中就是另外一种情况:

let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // 错误!s1 已经失效

  • 第一步产生一个 String 对象,值为 "hello"
  • 其中 "hello" 可以认为是类似于长度不确定的数据,需要在堆中存储
  • s1 变量消亡,想想所有权:一值一变量一所有

第二步的情况略有不同(这不是完全真的,仅用来对比参考):

image.png

克隆

  • 第一步产生一个 String 对象,值为 "hello"
  • 在堆中再创建一份数据,俩个变量各自指向不同的对象 image.png

函数中变量的所有权

fn main() {
    let s = String::from("hello");
    // s 被声明有效

    takes_ownership(s);
    // s 的值被当作参数传入函数
    // 所以可以当作 s 已经被移动,从这里开始已经无效

    let x = 5;
    // x 被声明有效

    makes_copy(x);
    // x 的值被当作参数传入函数
    // 但 x 是基本类型,依然有效
    // 在这里依然可以使用 x 却不能使用 s// 函数结束, x 无效, 然后是 s. 但 s 已被移动, 所以不用被释放


fn takes_ownership(some_string: String) { 
    // 一个 String 参数 some_string 传入,有效
    println!("{}", some_string);
} // 函数结束, 参数 some_string 在这里释放

fn makes_copy(some_integer: i32) { 
    // 一个 i32 参数 some_integer 传入,有效
    println!("{}", some_integer);
} // 函数结束, 参数 some_integer 是基本类型, 无需释放

总结:

  • 栈上的变量赋值是传递,赋值给函数的形参后还能使用直达生命周期结束
  • 指向堆上的变量根据所有权赋值的时候所有权发生转移

函数返回值的所有权机制

fn main() {
    let s1 = gives_ownership();
    // gives_ownership 移动它的返回值到 s1

    let s2 = String::from("hello");
    // s2 被声明有效

    let s3 = takes_and_gives_back(s2);
    // s2 被当作参数移动, s3 获得返回值所有权// s3 无效被释放, s2 被移动, s1 无效被释放.

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    // some_string 被声明有效

    return some_string;
    // some_string 被当作返回值移动出函数
}

fn takes_and_gives_back(a_string: String-> String { 
    // a_string 被声明有效

    a_string  // a_string 被当作返回值移出函数
}



总结:

  • 函数内在堆上申请的内存,如果所有权转移到函数外,出了函数的生命周期,只要还在被使用就不会被释放
  • 被当作函数返回值的变量所有权将会被移动出函数并返回到调用函数的地方,而不会直接被无效释放。

引用与租借

引用(Reference)实质上是变量的间接访问方式你可以把它看作一种指针。 & 运算符可以理解为拿到了变量的地址。

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    println!("s1 is {}, s2 is {}", s1, s2); // s1 is hello, s2 is hello
}

当一个变量的值被引用时,变量本身不会被认定无效。因为"引用"并没有在栈中复制变量的值。 结合上边的案例细说:

  • s1知道hello在堆上存放的地址,
  • 然后s1把自己的地址告诉了s2保存
  • 这时候依然符合所有权:一值一变量一所有的原则,两个变量保存的值都不一样

image.png

函数参数传递的道理一样:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);//The length of 'hello' is 5.
}

fn calculate_length(s: &String-> usize {
    s.len()
}

引用不会获得值的所有权,引用只能租借(Borrow)值的所有权。

引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权:

// 错误的转移
fn main() {
    let s1 = String::from("hello");
    let s2 = &s1; // 获取到s1的地址
    let s3 = s1; //  语法检查出错,因为s1正在被借用,不能被转移所有权
    println!("{}", s2);
}

// 
fn main() {
    let s1 = String::from("hello");
    let mut s2 = &s1; // s2需要声明为可变的变量,因为后面还需要重新的赋值
    let s3 = s1;
    s2 = &s3; // 重新从 s3 租借所有权
    println!("{}", s2);
}

好借好还,再借不难

fn main() {
    let s1 = String::from("run");
    let s2 = &s1; 
    println!("{}", s2);
    s2.push_str("oob"); // 错误,人情世故,不是自己的东西借来用用就好,不能乱修改,除非人家允许
    println!("{}", s2);
    
    let mut s1 = String::from("ok");// 借用前已经声明过了,损坏不用负责,这样的可以修改
    // s1 是可变的

    let s2 = &mut s1;
    // s2 是可变的引用

    s2.push_str("oob");
    println!("{}", s2);
}

可变引用不允许多重引用,但不可变引用可以

fn main() {
    let mut s = String::from("hello");
    // 下面的语法是错误的,想想一下俩个人同时修改一份数据,这样的是不是很糟糕,所以不允许
    let r1 = &mut s;
    let r2 = &mut s;
    println!("{}, {}", r1, r2);
    
}

垂悬引用(Dangling References)

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

看出来了吧:dangle函数中返回的是s的引用,但是s出了函数就已经释放了,这个引用就指向了空或者无效的地址,这样不确定的操作是不允许的,这样的引用称为垂悬引用。

切片 切片(Slice)是对数据值的部分引用。

fn main() {
    let s = String::from("broadcast");

    let part1 = &s[0..5];
    let part2 = &s[5..9];

    println!("{}={}+{}", s, part1, part2);// broadcast=broad+cast

    let mut s = String::from("runoob");
    let slice = &s[0..3];
    s.push_str("yes!"); // 错误,s 被部分引用,禁止更改其值。
    println!("slice = {}", slice);
    // String 转换成 &str
    let s1 = String::from("hello");
    let s2 = &s1[..];

}

image.png

  • ..y 等价于 0..y
  • x.. 等价于位置 x 到数据结束
  • .. 等价于位置 0 到结束
  • x..y 包含起始索引,不包含结束索引

tips : 尽量不要在包含中文字符的字符串中使用切片,因为并不是一个字符

在 Rust 中有两种常用的字符串类型:str 和 String。str 是 Rust 核心语言类型,就是本章一直在讲的字符串切片(String Slice),常常以引用的形式出现(&str)。凡是用双引号包括的字符串常量整体的类型性质都是  &str