Back
Featured image of post CS110L week1 writeup

CS110L week1 writeup

从零开始的 rust 课程学习

Part 1: Getting oriented

Rust 源码文件基本结构

|- src
|  |- main.rs
|- Cargo.toml

其中 Cargo.toml 文件内容

[package]
name = "hello-world"
version = "0.1.0"
authors = ["Ryan Eberhardt <reberhardt7@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

main.rs 文件内容

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

编译命令

$ cargo build

生成二进制文件为 target/debug/hello-world,文件名称由 toml 文件中的 name 设置

运行命令

$ cargo run

Part 2: Rust warmup

语法

Numeric types

有符号类型为 i8, i16, i32, i64i 表示 int,数字表示 bit 长度

对应的无符号类型为 u8, u16, u32, u64u 表示 unsigned

使用 let 声明变量

// int n = 1;
let n: i32 = 1;

Type inference

编译器可以自动识别变量类型,只有无法确定时,才需要显式声明

let n = 1;

variable and constants

数据类型默认是 const,定义变量时需要使用 mut 关键字

let mut n = 0;
n = n + 1;

Strings

字符串有两种类型,分别为 &strString

&str 指向内存中不可变的字符串(可以缺省),例如定义一个只读字符串:

let s: &str = "Hello world";

String 类型保存在 heap 中,需要使用 mutString 可缺省

let mut s: String = String::from("Hello");
s.push_str("world!");

String 追加内容后,编译器会自动根据需要 realloc 字符串所在的堆

在最后一次使用后,编译器会自动进行 free

Data Struct

vector

let mut v: Vec<i32> = Vec::new();
v.push(2);
v.push(3);

array

rust 中的数组长度是可变的

let mut arr: [i32; 4] = [0, 2, 4, 8];
arr[0] = -2;
println!("{}", arr[0] + arr[1]);

Loop

可以使用 in 遍历数组内容

for i in v.iter() {
    println!("{}", i);
}

While 循环

while i < 20 {
    i += 1;
}

While true 提供了特殊的 loop 循环,这个关键字有助于编译器对变量初始化做一些推测

loop {
    i += 1;
    if i == 10 { break; }
}

Function

  • 需要指定函数类型,编译器无法推测
  • 函数可以没有 return 关键字,最后不加分号即可
fn sum(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    // do stuff...
}

rust 中所有东西都是表达式,主要是受到了函数式编程的影响

// int x = someBool ? 2 : 4;
let x = if someBool {2} else {4};

在这一思想的影响下,函数是一个由分号分隔的表达式序列,并对最后一个表达式的值进行评估

所以结尾的 无分号单一表达式 会被认为是返回值

例如:

fn fib(n: i32) -> i32 {
    if n <= 1 { n } else { fib(n-1) + fib(n-2) }
}

练习

实现三个函数

add_n: 输入一个 vec 和数字 n,为 vec 中每个数字加上 n,并返回一个新 vec

add_n_inplace: 直接修改 vec 中的内容,不返回

dedup: 从一个 vec 中删除重复元素,保留第一个出现的元素(使用 HashSet)

fn add_n(v: Vec<i32>, n: i32) -> Vec<i32> {
    // unimplemented!()
    let mut ret_v: Vec<i32> = Vec::new();
    for i in v.iter() {
        ret_v.push(i + n);
    }
    return ret_v;
}

fn add_n_inplace(v: &mut Vec<i32>, n: i32) {
    // unimplemented!()
    for i in 0..v.len() {
        v[i] += n;
    }
}

fn dedup(v: &mut Vec<i32>) {
    // unimplemented!()
    let mut hs = HashSet::new();
    let mut x = 0;
    let len = v.len();
    for _ in 0..len {
        if hs.contains(&(v[x])) {
            v.remove(x);
        } else {
            hs.insert(v[x]);
            x += 1;
        }
    }
}

Part 3: hangman

写个猜词游戏的代码,直接放源码了,肯定有更好的写法,不过也没必要

// Simple Hangman Program
// User gets five incorrect guesses
// Word chosen randomly from words.txt
// Inspiration from: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
// This assignment will introduce you to some fundamental syntax in Rust:
// - variable declaration
// - string manipulation
// - conditional statements
// - loops
// - vectors
// - files
// - user input
// We've tried to limit/hide Rust's quirks since we'll discuss those details
// more in depth in the coming lectures.
extern crate rand;
use rand::Rng;
use std::fs;
use std::io;
use std::io::Write;

const NUM_INCORRECT_GUESSES: u32 = 5;
const WORDS_PATH: &str = "words.txt";

fn pick_a_random_word() -> String {
    let file_string = fs::read_to_string(WORDS_PATH).expect("Unable to read file.");
    let words: Vec<&str> = file_string.split('\n').collect();
    String::from(words[rand::thread_rng().gen_range(0, words.len())].trim())
}

fn so_far_guess(word: Vec<char>, history_guessed: String) -> String {
    let mut x = String::new();
    for w in word {
        if history_guessed.contains(w) {
            x.push(w);
        } else {
            x.push('-');
        }
    }
    return x;
}

fn guess_success(word: Vec<char>, history_guessed: String) -> bool {
    for w in word {
        if !history_guessed.contains(w) {
            return false;
        }
    }
    return true;
}

fn main() {
    let secret_word = pick_a_random_word();
    // Note: given what you know about Rust so far, it's easier to pull characters out of a
    // vector than it is to pull them out of a string. You can get the ith character of
    // secret_word by doing secret_word_chars[i].
    let secret_word_chars: Vec<char> = secret_word.chars().collect();
    // Uncomment for debugging:
    println!("random word: {}", secret_word);

    // Your code here! :)

    let mut your_left_guesses = NUM_INCORRECT_GUESSES;
    let mut your_guessed_letters = String::new();

    while your_left_guesses > 0 {

        println!("The word so far is {}", so_far_guess(secret_word_chars.clone(), your_guessed_letters.clone()));
        println!("You have guessed the following letters: {}", your_guessed_letters);
        println!("You have {} guesses left", your_left_guesses);
        print!("Please guess a letter: ");
        io::stdout()
            .flush()
            .expect("Error flushing stdout.");
        let mut guess = String::new();
        let mut cur_guess: char;
        loop {
            io::stdin()
                .read_line(&mut guess)
                .expect("Error reading line.");
            if guess.len() != 0 {
                cur_guess = guess.remove(0);
                if !your_guessed_letters.contains(cur_guess) {
                    your_guessed_letters.push(cur_guess);
                    break;
                }
            }
        }
        
        if !secret_word.contains(cur_guess) {
            your_left_guesses -= 1;
            println!("Sorry, that letter is not in the word");
        }

        if guess_success(secret_word_chars.clone(), your_guessed_letters.clone()) {
            break;
        }
        println!();
    }
    if your_left_guesses > 0 {
        println!("Congratulations you guessed the secret word: {}!", secret_word);
    } else {
        println!("Sorry, you ran out of guesses!");
    }
}
Built with Hugo
Theme Stack designed by Jimmy