Bootstrap

Rust隐式返回(最后一个表达式后不加分号)与Rust显式返回(Rust return)(Rust隐示返回、Rust显示返回)

Rust中的隐式返回与显式返回

在Rust编程语言中,函数的返回值可以通过隐式返回和显式返回两种方式实现。理解这两种返回方式对于编写高效、简洁的Rust代码至关重要。

一、隐式返回

1. 什么是隐式返回

隐式返回是指在函数或代码块的最后一个表达式后不加分号,该表达式的值将自动作为返回值返回。Rust的函数默认采用隐式返回,鼓励开发者编写更加简洁的代码。

2. 隐式返回的语法示例

fn add(a: i32, b: i32) -> i32 {
    a + b  // 没有分号,返回 a + b 的值
}

在上述代码中,a + b 是函数 add 的最后一个表达式且没有分号,因此其结果将作为函数的返回值。

3. 在代码块中的隐式返回

隐式返回不仅适用于函数,也适用于代码块。例如:

let result = {
    let x = 5;
    x + 3  // 返回 8
};

二、显式返回(Rust return)

1. 什么是显式返回

显式返回是指在函数的任意位置使用 return 关键字立即返回一个值。显式返回通常用于需要在某个条件下提前退出函数的情况。

2. 显式返回的语法示例

fn find_even(numbers: &[i32]) -> Option<i32> {
    for &num in numbers {
        if num % 2 == 0 {
            return Some(num);  // 提前返回第一个偶数
        }
    }
    None  // 未找到偶数,返回 None
}

在这个例子中,当找到第一个偶数时,使用 return 立即返回结果。

三、隐式返回与显式返回的比较

1. 使用场景

  • 隐式返回:适用于函数或代码块的自然结束,没有提前退出的需求。
  • 显式返回:适用于需要在满足某个条件时立即退出函数,并返回特定值的情况。

2. 可读性与简洁性

  • 隐式返回:代码更简洁,但可能在复杂逻辑中不够直观。
  • 显式返回:代码更明确,特别是在处理多重条件时,提高可读性。

四、示例分析

1. 使用显式返回的函数

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];  // 找到空格,提前返回
        }
    }

    &s[..]  // 未找到空格,返回整个字符串
}

2. 尝试移除显式返回

有人可能认为可以移除 return,改为:

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            &s[0..i]  // 没有 return,这样可以吗?
        }
    }

    &s[..]
}

3. 问题分析

  • 在上述修改中,&s[0..i] 的值并没有被使用或返回。
  • if 块中的最后一个表达式并不会自动作为函数的返回值,除非整个函数体是一个表达式。
  • 因此,移除 return 后,函数不会在找到空格时提前返回,导致逻辑错误。

五、正确的代码结构

1. 保留显式返回

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

2. 使用迭代器改写

可以利用迭代器的方法,避免显式使用 return

代码示例1(使用find函数)
fn first_word(s: &str) -> &str {
    let pos = s.find(' ').unwrap_or_else(|| s.len());
    &s[..pos]
}
代码解释

这个代码定义了一个 Rust 函数 first_word,用于从字符串 s 中提取第一个单词。下面是一步一步的解释:

代码分析:
fn first_word(s: &str) -> &str {
    let pos = s.find(' ').unwrap_or_else(|| s.len());
    &s[..pos]
}
  1. fn first_word(s: &str) -> &str
    这行代码声明了一个名为 first_word 的函数。

    • s: &str 是输入参数,表示传入的是一个字符串切片(&str)。
    • -> &str 表示函数的返回值也是一个字符串切片,即该函数返回的是 s 中的第一个单词部分。
  2. let pos = s.find(' ').unwrap_or_else(|| s.len());

    • s.find(' '):尝试在字符串 s 中找到第一个空格字符的位置。如果找到了,返回空格的索引(位置)。
    • .unwrap_or_else(|| s.len()):如果没有找到空格(即 find 返回 None),那么使用 s.len()。这意味着如果字符串没有空格,pos 将会是字符串的长度,也就是字符串的结尾位置。

    作用:这行代码确定了第一个空格的位置。如果没有空格,就返回字符串的长度,表示整个字符串都是第一个单词。

  3. &s[..pos]

    • 这部分是字符串切片操作,它取出从字符串开头到 pos 位置(不包括 pos 的字符)的子字符串。也就是提取字符串的第一个单词。
    • 如果字符串中有空格,pos 就是空格的位置,因此提取到空格前的部分;如果没有空格,pos 就是字符串的长度,因此提取整个字符串。
示例:
  • 输入:"Hello world"

    • find(' ') 找到空格的位置(5),所以 pos 为 5。
    • &s[..5] 提取出 "Hello"
  • 输入:"Rust"

    • find(' ') 找不到空格,所以 poss.len(),即 4。
    • &s[..4] 提取出 "Rust"
总结:

这个函数的作用是返回字符串 s 中的第一个单词。如果字符串没有空格,则返回整个字符串。如果有空格,则返回空格前的部分。

代码示例2(转为字节数组处理)

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    let pos = bytes.iter()
        .position(|&item| item == b' ')
        .unwrap_or(bytes.len());
    &s[..pos]
}
代码解释

这段代码定义了一个 Rust 函数 first_word,它的功能是从给定的字符串 s 中提取并返回第一个单词。我们逐步解释代码的各个部分。

fn first_word(s: &str) -> &str {
1. 函数签名
  • fn first_word(s: &str) -> &str 表示定义一个函数,函数名是 first_word,它接受一个参数 s,类型是 &str(Rust 中的字符串切片类型)。
  • 函数的返回值类型是 &str,即返回一个字符串切片。
2. 获取字符串的字节数组
let bytes = s.as_bytes();
  • s.as_bytes() 方法将 s 转换成一个字节数组(Vec<u8>)。在 Rust 中,字符串是以 UTF-8 编码的,因此每个字符在内存中通常由一个或多个字节表示。
3. 查找第一个空格的位置
let pos = bytes.iter()
    .position(|&item| item == b' ')
    .unwrap_or(bytes.len());
  • bytes.iter() 会返回一个字节迭代器,允许你遍历字符串的每个字节。
  • position(|&item| item == b' ') 是一个闭包,它会检查每个字节是否等于空格(b' ')。b' ' 是字节字面量,它等价于数字值 32(空格的 ASCII 值)。
    • 这个方法返回第一个空格字符的字节位置(索引)。如果找到了空格,返回的是该空格的字节索引;如果没有找到空格,它返回 None
  • .unwrap_or(bytes.len()) 是用来处理如果没有找到空格时的情况。unwrap_or 会返回 bytes.len(),即字符串的总长度。这意味着如果没有空格,整个字符串就会被视为第一个单词。
4. 返回第一个单词
&s[..pos]
  • 最后,&s[..pos] 创建一个新的字符串切片,从字符串 s 的起始位置到 pos 位置(不包括 pos)提取一个子字符串。
    • 如果找到了空格,那么 pos 会是第一个空格的位置,&s[..pos] 就是字符串的第一个单词。
    • 如果没有空格,pos 就是字符串的长度,&s[..pos] 就是整个字符串。
总结

这个函数的目的是从一个字符串 s 中提取并返回第一个单词(即从字符串的起始位置到第一个空格之间的部分)。如果字符串中没有空格,函数会返回整个字符串。

例如:

  • first_word("hello world") 会返回 "hello"
  • first_word("rust") 会返回 "rust"

六、总结

  • 隐式返回适用于函数末尾的返回,能使代码更简洁。
  • 显式返回适用于需要提前退出函数的情况,代码更直观明确。
  • 在编写Rust代码时,应根据具体需求选择合适的返回方式,确保代码逻辑正确、可读性高。

七、深入思考

1. 编译器行为

  • Rust编译器对于函数的返回值有严格的检查,确保返回类型与函数签名一致。
  • 隐式返回需要注意最后一个表达式的类型和位置。

2. 函数式编程风格

  • Rust鼓励使用函数式编程风格,隐式返回符合这一理念。
  • 然而,在处理复杂逻辑时,显式返回可能更加合适。

3. 错误处理

八、最佳实践

  • 清晰性优先:代码的可读性和清晰性应当优先于简洁性。
  • 合理使用返回方式:根据函数的逻辑需求,选择适当的返回方式。
  • 代码审查:通过代码审查和测试,确保返回方式的选择不影响程序的正确性。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;