速成课第1节-练习解答

下面是上一节 Rust 速成课的练习解答。

这篇文章是基于 FP 完成 Rust 教学系列的一部分。 如果你在博客之外阅读这篇文章,你可以在介绍文章的顶部找到这个系列中所有文章的链接。 也可订阅 RSS 频道。

练习1

以下是已损坏的代码:

fn main() {
    let val: String = String::from("Hello, World!");
    printer(val);
    printer(val);
}

fn printer(val: String) {
    println!("The value is: {}", val);
}

我们收到了一条关于 move 的错误信息。 我们将在下一课讨论所有权时学习更多的 move。 有两种基本的解决方案。 首先,不太理想的一点是:

Clone

我们已经将原始 val 移动到第一个 printer() 调用中,并且不能再次使用它。 一种变通方法是将 val 的一个克隆移动到那个调用中,不影响原始的代码:

fn main() {
    let val: String = String::from("Hello, World!");
    printer(val.clone());
    printer(val);
}

fn printer(val: String) {
    println!("The value is: {}", val);
}

请注意,我只是在第一次克隆了 val,而不是第二次。 我们不需要在第二次调用之后再次使用 val,所以 move 它是安全的。 使用额外的克隆开销很大,因为它需要分配内存和执行缓冲区副本。

说起来很贵...

引用传递

与 move 值不同,我们可以通过引用将其传递给 printer() 函数。 让我们首先尝试通过修改 printer() 来实现这一点:

fn main() {
    let val: String = String::from("Hello, World!");
    printer(val);
    printer(val);
}

fn printer(val: &String) {
    println!("The value is: {}", val);
}

这不起作用,因为当我们调用 printer 时,我们仍然给它一个 String,而不是对 String 的引用。 解决这个问题非常简单:

fn main() {
    let val: String = String::from("Hello, World!");
    printer(&val);
    printer(&val);
}

请注意,“&“ 符号的意思是:

  • 对此类型的引用

  • 对这个值进行引用

还有一个更好的方法来写 printer:

fn printer(val: &str) {
    println!("The value is: {}", val);
}

使用 str 而不是 String,我们可以传递字符串文本,而不需要强制分配堆对象。 我们将在讨论字符串时更详细地讨论这个问题。

练习2

以下是已损坏的代码:

fn main() {
    let i = 1;

    loop {
        println!("i == {}", i);
        if i >= 10 {
            break;
        } else {
            i += 1;
        }
    }
}

我们从编译器得到的错误信息非常有用:

不能两次赋值给不可变变量 i

为了解决这个问题,我们将变量从不可变变为可变:

fn main() {
    let mut i = 1;
    ...

练习3

这个练习问你什么时候可以省略分号,这里有一个简单的规则:

  • 是块中的最后一个语句

  • 表达式的类型是 unit

例如,在这段代码中,删除分号就可以了:

fn main() {
    for i in 1..11 {
        println!("i == {}", i);
    }
}

也就是说,像这些纯粹有效的表达上留下分号似乎有些习惯用法。

练习4

这个练习是为了在 Rust 中实现 FizzBuzz,在这里重复以下规则:

  1. 打印数字1到100

  2. 如果数字是3的倍数,输出 fizz 而不是数字

  3. 如果数字是5的倍数,输出 buzz 而不是数字

  4. 如果数字是3和5的倍数,输出fizzbuzz而不是数字

这里有一个使用 if/else 回退的可能解决方案:

fn main() {
    for i in 1..101 {
        if i % 3 == 0 && i % 5 == 0 {
            println!("fizzbuzz");
        } else if i % 3 == 0 {
            println!("fizz");
        } else if i % 5 == 0 {
            println!("buzz");
        } else {
            println!("{}", i);
        }
    }
}

这至少有一个缺点:它将可能需要多次测试 i%3 == 0 和 i%5 == 0。在表内部,编译器可以优化它。但是,重复的模数调用仍然只是嘲笑我们的代码!我们可以改为使用模式匹配:

fn main() {
    for i in 1..101 {
        match (i % 3 == 0, i % 5 == 0) {
            (true, true) => println!("fizzbuzz"),
            (true, false) => println!("fizz"),
            (false, true) => println!("buzz"),
            (false, false) => println!("{}", i),
        }
    }
}

或者,如果你想用通配符匹配来获得一些乐趣:

fn main() {
    for i in 1..101 {
        match (i % 3, i % 5) {
            (0, 0) => println!("fizzbuzz"),
            (0, _) => println!("fizz"),
            (_, 0) => println!("buzz"),
            (_, _) => println!("{}", i),
        }
    }
}

我不会告诉您哪个是 “最佳” 解决方案。当然,可以尝试其他实现。这是为了给您一些更多Rust结构的感觉。

最后更新于