用 rustlings 练习 Rust 语言

alt
// This is a quiz for the following sections:
// - Variables
// - Functions
// - If
//

exercises/variables/variables1.rs

Variables

In Rust, variables are immutable(不可变的) by default.

When a variable is immutable, once a value is bound to a name, you can’t change that value.

You can make them mutable by adding mut in front of the variable name.

Further information

alt

exercises/variables/variables2.rs

alt
  • 变量

  • 函数

exercises/variables/variables3.rs

  • 错误
 --> exercises/variables/variables3.rs:9:27
  |
8 |     let x: i32;
  |         - binding declared here but left uninitialized
9 |     println!("Number {}", x);
  |                           ^ `x` used here but it isn't initial

在 Rust 中,这段代码存在一个主要问题:变量 x 被声明了但没有初始化。

Rust 要求所有变量在使用前必须被初始化,即使是显式声明了类型但没有赋值的变量

c++ 没有初始化 不会报错,但是 默认不一定是0 可能是随机值。 因此 自己写代码 默认一个值

void exampleFunction() {
    int localVar; // localVar 是未初始化的
    std::cout << localVar; // 未定义行为,可能导致程序崩溃或不正确的输出
}
  • 修改
// variables3.rs
//
// Execute `rustlings hint variables3` or use the `hint` watch subcommand for a
// hint.


fn main() {
    let x: i32 = 1;
    // 在c++中 默认值 int a;  可能是 0 可能是其他很大值 但是不会编译报错

    println!("Number {}", x);
}

exercises/variables/variables4.rs:

  • what
 |     let x = 3;
   |         -
   |         |
   |         first assignment to `x`
   |         help: consider making this binding mutable: `mut x`
10 |     println!("Number {}", x);
11 |     x = 5; // don't change this line
   |     ^^^^^ cannot assign twice to immutable variable
  • why

在 Rust 中,这段代码会引发一个编译错误。原因在于 Rust 的变量默认是不可变的(immutable),这意味着一旦声明并赋予初始值后,你不能再次修改它,除非你明确地将其声明为可变的。

以下是你的代码中的问题:

  1. 变量 x 被声明并初始化为 3
  2. 尝试将 x 重新赋值为 5,但由于 x 没有被声明为可变的,这会导致编译错误。

要修复这个问题,你需要使用 mut 关键字将 x 声明为可变变量,如下所示:

fn main() {
    let mut x = 3// 使用 mut 使 x 成为可变变量
    println!("Number {}", x);
    x = 5// 现在可以给 x 重新赋值
    println!("Number {}", x);
}

这段代码现在将正确编译并运行,首先打印 x 的初始值 3,然后将其值更改为 5 并打印新的值。

exercises/variables/variables5.rs

在 Rust 中,这段代码存在几个问题:

  1. 变量 number 被声明为一个字符串 "T-H-R-E-E",这意味着它的类型是 &str(字符串切片)。
  2. 尝试将 number 重新赋值为一个整数 3,这是不允许的,因为 number 已经被声明为字符串类型,不能直接赋值为整数。
  3. 即使 number 是一个整数, number + 2 这样的表达式也是不允许的,因为 Rust 是一种静态类型语言,它不允许隐式类型转换。你需要显式地转换类型。

要修复这些问题,你可以创建一个新的变量来存储整数,然后将其与 2 相加。例如:

fn main() {
    let number = "T-H-R-E-E"// 保持不变
    println!("Spell a Number : {}", number);
    let number = 3// 创建一个新的变量来存储整数
    println!("Number plus two is : {}", number + 2);
}

但请注意,在 Rust 中,你不能有相同作用域内的两个变量使用相同的名称。如果你想要同时使用字符串和整数,你需要使用不同的变量名,或者使用更复杂的数据结构(如元组或结构体)来存储不同类型的值。

例如,如果你想要在同一个作用域内使用字符串和整数,你可以使用元组:

fn main() {
    let number_str = "T-H-R-E-E";
    println!("Spell a Number : {}", number_str);
    let number_tuple: (i32i32) = (32); // 使用元组来存储两个整数
    println!("Number plus two is : {}", number_tuple.0 + number_tuple.1);
}

在这个例子中,我们使用元组 (number_tuple.0, number_tuple.1) 来存储整数,并使用 .0.1 来访问元组中的值。然后,我们打印出 number_tuple.0 加上 number_tuple.1 的结果。

alt

在 Rust 中,这段代码看起来是正确的,但是根据注释提示,可能存在一些不明显的问题。注释中提到的 "Data Types" 提示我们可能需要考虑类型的问题。

问题在于 println! 宏的占位符和传递给它的参数类型不匹配。println! 宏中的占位符 "Number {}" 期望一个可以被格式化为 Display 的类型,但是 NUMBER 被声明为一个 const 整数,其类型是 i32。整数类型不能直接被格式化为字符串,因此你需要使用 {} 占位符来打印整数。

以下是修正后的代码:

// Data Types
const NUMBER: i32 = 3// 明确 NUMBER 的类型为 i32
fn main() {
    println!("Number {}", NUMBER); // 使用 {} 占位符正确地打印整数
}

此外,虽然在 Rust 中 const 的类型可以被推断出来,但是显式地声明类型是一个好习惯,特别是在示例代码或教学材料中,因为它可以提高代码的可读性。

这段代码现在将正确编译并运行,打印出 "Number 3"。如果 NUMBER 需要被用作字符串,你需要将其声明为一个字符串字面量,例如:

const NUMBER: &str = "3";
fn main() {
    println!("Number {}", NUMBER); // 现在 NUMBER 是一个字符串,可以正确地格式化
}

这样 NUMBER 就是一个字符串切片,可以被 println! 宏中的 {} 占位符正确地格式化和打印。

Functions

Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even in more complex code.

Further information

exercises/functions/functions1.rs

error[E0425]: cannot find function `call_me` in this scope
 --> exercises/functions/functions1.rs:9:5
  |
9 |     call_me();
  |     ^^^^^^^ not found in this scope

在 Rust 中,这段代码存在一个问题:call_me 函数在 main 函数中被调用,但是没有定义。为了使这段代码能够正确编译和运行,你需要定义一个名为 call_me 的函数,它可以是任何形式的函数,例如一个简单的打印函数。

下面是如何定义 call_me 函数的一个示例:

fn call_me() {
    println!("Called!");
}

fn main() {
    call_me();
}

在这个示例中,call_me 是一个没有参数也没有返回值的函数,它使用 println! 宏打印 "Called!" 到控制台。然后,在 main 函数中调用了 call_me 函数。

如果你想要 call_me 函数具有一些功能,你可以添加参数和/或返回值。例如:

fn call_me() -> String {
    String::from("Called!")
}

fn main() {
    let message = call_me();
    println!("{}", message);
}

在这个例子中,call_me 函数返回一个 String 类型的值 "Called!"。然后在 main 函数中,我们调用 call_me 并将其返回值赋给变量 message,然后打印这个变量。

确保你的 call_me 函数定义在 main 函数之前或在同一个作用域内,以便 main 函数能够调用它。

在 Rust 编程语言中,fn 是用来声明一个函数的关键字。当你想要创建一个新的函数时,你需要使用 fn 来开始函数的定义,后面跟着函数的名称、参数列表(如果有的话),以及函数体。

函数的基本语法如下:

fn function_name(parameters) -> return_type {
    // 函数体
}
  • function_name 是你为函数指定的名称。
  • parameters 是函数的参数列表,参数之间用逗号分隔。如果函数不需要参数,可以省略这个部分。
  • return_type 是函数返回值的类型。如果函数不返回任何值,可以使用 () 表示返回空元组。
  • 函数体是大括号 {} 内的代码,这里是函数执行的操作。

下面是一些函数的示例:

  1. 一个没有参数也不返回任何值的函数:
fn say_hello() {
    println!("Hello!");
}
  1. 一个带有一个参数的函数,不返回任何值:
fn print_number(number: i32) {
    println!("The number is: {}", number);
}
  1. 一个没有参数但返回一个 i32 类型值的函数:
fn add_two_numbers(a: i32, b: i32) -> i32 {
    a + b
}
  1. 一个带有两个参数并返回一个 String 类型值的函数:
fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

在这些示例中,fn 关键字用于定义函数的开始,函数名后面跟着参数列表和返回类型(如果有的话),然后是函数体,它包含了函数执行的具体操作。

functions2.rs

alt

functions3.rs

alt

这段 Rust 代码中存在几个问题:

  1. 函数调用与定义不匹配:在 main 函数中,call_me() 被调用时没有传递任何参数,但是在 call_me 函数的定义中,它期望一个类型为 u32 的参数 num

  2. 类型注解错误:在 call_me 函数的定义中,参数列表里的 num: 后面缺少了参数名。正确的语法应该是 fn call_me(num: u32)

  3. 逻辑问题for 循环中的 i + 1 会在打印时导致从 1 开始计数,这是正确的,但如果 num 是 0,这将导致无限循环,因为 0..num 的范围是空的,没有元素可以迭代。

修正这些问题后的代码如下:

fn main() {
    call_me(5); // 传递一个 u32 类型的参数给 call_me 函数
}

fn call_me(num: u32) { // 正确地声明了参数名和类型
    for i in 1..num { // 从 1 开始迭代,以避免当 num 为 0 时的无限循环
        println!("Ring! Call number {}", i);
    }
}

在这个修正后的版本中:

  • main 函数现在传递了一个 u32 类型的参数 5call_me 函数。
  • call_me 函数正确地声明了参数 num 并移除了错误的冒号。
  • for 循环现在从 1 开始迭代到 num - 1,这避免了当 num 为 0 时的潜在无限循环问题,并且打印的 "Ring! Call number" 将从 1 开始计数。
alt

在 Rust 语言中,函数的返回值可以显式地使用 return 关键字来返回,也可以通过函数签名末尾的类型来隐式地返回值。这里有两种情况:

  1. 显式返回:使用 return 关键字来返回一个值。这是大多数编程语言中常见的做法。

    fn sum(a: i32, b: i32) -> i32 {
        return a + b;
    }
  2. 隐式返回:在 Rust 中,如果函数的最后一个表达式是一个没有被分号 ; 终止的表达式,那么这个表达式的值将被用作函数的返回值。这种方式可以使得代码更加简洁。

    fn sum(a: i32, b: i32) -> i32 {
        a + b
    }

在上面的第二个例子中,由于 a + b 是函数中的最后一个表达式,并且没有使用分号,所以它的值将被隐式地返回。

需要注意的是,如果函数的返回类型是 ()(即单元类型),则不需要返回任何值,无论是显式还是隐式。

fn do_nothing() {
    // 这个函数没有返回值,因为其返回类型是 ()
}

这种设计使得 Rust 代码在某些情况下更加简洁,同时也保持了类型安全和表达式意图的清晰。

alt

If

if, the most basic (but still surprisingly versatile!) type of control flow, is what you'll learn here.

Further information

alt

f2.rs

  --> exercises/if/if2.rs:14:9
   |
10 | pub fn foo_if_fizz(fizzish: &str) -> &str {
   |                                      ---- expected `&str` because of return type
...
14 |         1
   |         ^ expected `&str`, found integer

在您的 if2.rs 代码中,foo_if_fizz 函数试图根据传入的字符串返回不同的结果。但是,存在几个问题需要解决:

  1. 函数的返回类型是 &str,但是 else 分支返回了一个整型 1,这会导致类型不匹配错误。
  2. 需要实现 else 分支返回的字符串,以满足测试 bar_for_fuzzdefault_to_baz 的要求。

下面是修复后的代码:

pub fn foo_if_fizz(fizzish: &str) -> &str {
    if fizzish == "fizz" {
        "foo"
    } else if fizzish == "fuzz" {
        "bar"
    } else {
        "baz"
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn foo_for_fizz() {
        assert_eq!(foo_if_fizz("fizz"), "foo");
    }

    #[test]
    fn bar_for_fuzz() {
        assert_eq!(foo_if_fizz("fuzz"), "bar");
    }

    #[test]
    fn default_to_baz() {
        assert_eq!(foo_if_fizz("literally anything"), "baz");
    }
}

在这个修复版本中,我添加了一个新的 else if 分支来处理当 fizzish"fuzz" 时返回 "bar"。同时,对于所有其他情况,使用 else 分支返回 "baz"。这样,所有的测试都应该能够通过。

确保您的测试函数中的断言调用是正确的,并且测试用例的名称与测试内容相匹配。在您的原始代码中,测试用例 bar_for_fuzzdefault_to_baz 的名称可能会让人有些困惑,因为它们似乎暗示了不同的行为,但实际上它们都期望返回 "bar""baz"。在修复后的代码中,我已经假设测试用例的名称是正确的,并且它们分别测试了当输入为 "fuzz" 和任何其他字符串时的行为。

alt
alt
/**
【昵称】小王同学
【坐标】山东
【自我介绍】
    1. 高级后端工程师,擅长c++并发编程。
    2. 专注分布式存储,分布式数据库。
    3. 时间的践行者,立志成为一名讲师。
【我的成绩】
    1.  为了走出农村,2次高考
         一次考研失败,
         遇挫之后不知道干什么,开启被动之旅。
    2. 为了找到合适工作,   
        深入研究c++书籍和leetcode 200题目
    3. 为了提高项目能力,参与开源项目建设。
    4. 为了把简单事情说清楚/要干啥
        按照《只管去做》,《福格行为模型>>方法。
        纸上得来终觉浅,绝知此事要躬行
        做一个践行者。
【我能提供】
    1.  后端程序员的简历优化+就业辅导+职业规划
    2.  全栈工程师(c++,rust,go,python )项目开发
    3. 一年践行12本书践行记录。
【希望一起解决什么,开启破圈之旅】
    1. 交接更多朋友,抱团取暖。
        寻找意义本身就更加有意义。
    2. 无法做整个系统,聚焦一个模块
         道可道也,非恒道也 
         名可名也,非恒名也。
         无名 万物之始也
         有名 万物之母也
         别想太多,只管去做,躬身入局
     
链接我: # 微信(github):watchpoints   
        #公众号:后端开发成长指南
**/

本文由 mdnice 多平台发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值