rust 为什么在函数中使用return作为最后一条语句被认为是不好的风格?

h7appiyu  于 2023-01-30  发布在  其他
关注(0)|答案(6)|浏览(176)

我正在阅读Rust文档,并遇到了以下示例和语句
使用return作为函数的最后一行是可行的,但被认为是不好的风格:

fn foo(x: i32) -> i32 {
    if x < 5 { return x; }

    return x + 1;
}

我知道我本可以把上面写为

fn foo(x: i32) -> i32 {
    if x < 5 { return x; }

    x + 1
}

但我更倾向于写前者,因为它更直观,我确实理解函数返回值应该作为表达式使用,所以后者可以工作,但为什么不鼓励前者呢?

svmlkihl

svmlkihl1#

复制自redditWhy isn't the syntax of return statements explicit?
来自@pcwalton的回答
闭包中的显式返回确实很烦人,例如,在ES6的箭头函数引入之前,这是JavaScript中的一大难题

myArray.map(function(x) { return x * 2; })

即使没有function关键字,也是多余的。一旦你的语言中有了隐式返回,为了一致性,你也可以到处都有它们。它使代码更简洁的事实只是一个额外的好处。
以及来自@mozilla_kmc
Rust是一种面向表达式的语言。块的形式为

{
    stmt;
    stmt;
    ...
    stmt;
    expr
}

语句基本上是表达式或let绑定,如果未指定,则尾随表达式隐式为(),整个块的值就是最后一个表达式的值。
这不仅仅是函数。你可以写

let foo = if x { y } else { z };

所以if也代替了C的?:操作符,每种块的工作方式都是一样的:

let result = unsafe {
    let y = mem::transmute(x);
    y.frob()
};

因此,函数末尾的隐式返回是Rust面向表达式语法的自然结果。改进的人体工程学只是一个不错的奖励:)
谜题:return x本身是一个表达式--它的值是什么?
答案(@ dubousjim建议):
它是一个永不类型!

rbl8hiat

rbl8hiat2#

它只是 * 是 *。
约定不需要有特别好的理由,它们只需要是普遍接受的约定。碰巧的是,这个约定确实有一个相对好的理由--它比较短,因为你没有return;。你可能认为return x + 1;更直观,但我强烈反对--它真的很烦人,我觉得迫切需要修正它。我说这句话是作为一个谁,在开始使用Rust之前,从来没有使用过面向表达式的语言。在编写Python时,return x + 1在那个地方看起来是正确的,而在编写Rust时,它看起来是错误的。
现在碰巧的是,该代码可能应该这样编写:

fn foo(x: i32) -> i32 {
    if x < 5 {
        x
    } else {
        x + 1
    }
}

这强调了语言的表达取向。

fcipmucu

fcipmucu3#

clippy lint为needless_return lint提供了以下理由:
删除回车和分号会使代码更加生疏。
这可能是我们所能得到的最好的客观理由。
就直觉而言我觉得它是由我们的个人经验塑造的,因此是主观的。虽然Rust本身不是函数式编程语言,但许多使用和开发它的人似乎都有很强的函数式编程语言背景,如Haskell,它完全基于表达式。Rust的许多领域都能强烈地感受到这种影响(eidogg.错误处理)。所以对他们来说(老实说,包括我自己)使用表达式而不是语句似乎更优雅。

htrmnn0y

htrmnn0y4#

    • 我强烈怀疑它源自函数式编程风格。**(根据@Markus Klein的回答)。

在OCaml中,您的示例:

let foo x = if x < 5 then x else x + 1

与您在Rust中修改的示例进行比较:

fn foo(x: i32) -> i32 {
    if x < 5 { x } else { x + 1 }
}

OCaml中的闭包示例:

fun x -> if x < 5 then x else x + 1

与Rust闭合比较:

|x| if x < 5 { x } else { x + 1 }

所以Rust似乎采用了函数式编程风格。

    • 为什么函数式编程语言要这么做?**我不确定。可能是因为到处扔return会很烦人,而且意义从代码中已经很清楚了。

函数式编程语言实际上并不依赖于经典的"赋值给变量,操作变量,返回变量"范式,而是使用"赋值给变量,通过几个不同的函数传输变量,最终结果是输出"范式(甚至赋值给变量也是可选的:向上搜索"无点编程")。
Rust中的"经典"范例:

fn qux(mut x: i32) -> i32 {
x = foo(x);
x = bar(x);
return x; // classic C style
}

与OCaml中的函数式编程范例:

let qux x = x |> foo |> bar

如果我们在OCaml例子中需要一个return,我们需要把它放在最前面,这在很大程度上是没有意义的,因为我们已经知道它将返回什么。

    • 因此,这种函数式编程风格很可能是由于上述原因而延续到Rust中的。**

示例:
Rust的if语句的求值结果是一个表达式,这与C("Classic")不同,与OCaml(Functional)类似。

93ze6v8z

93ze6v8z5#

与流行的OOP语言不同,Rust是EOP,其中EOP = * 面向表达式 * 编程
当以;结尾时,那是一个表达式,因此block是一个表达式。
相反,当没有;而结束时,这是一个语句。
我们写了一个简单的加1函数

fn add_one(x: i32)  {
    x + 1;
}

fn main() {
    println!("{:#?}", add_one(1));
}

./target/debug/mytest
()

我们去掉;

fn add_one(x: i32)  {
    x + 1
}
    
fn main() {
    println!("{:#?}", add_one(1));
}

./target/debug/mytest //can't do this

无法编译,因为函数的类型 tuplei32 不匹配
我们修改函数,

fn add_one(x: i32) -> i32 {
    x + 1
}

fn main() {
    println!("{:#?}", add_one(1));
}

./target/debug/mytest
2

有人说这是隐式return,但我把 -〉 当作 return
此外,我们可以通过添加 return 强制type进入i32

fn add_one(x: i32) -> i32 {
    return x + 1; //without return, type mismatched
}

fn main() {
    println!("{:#?}", add_one(1));
}

./target/debug/mytest
2

现在我们做一个选择

fn add_one(x: i32, y: i32, s: i32) -> i32 {
    if s == 1 {
        x + 1
    } else if s == 2 {
        y + 1
    } else {
        0
    }
}

fn main() {
    println!("{:#?}", add_one(1, 2, 1));// choose x, not y
}

不良风格而言,这主要是在这些条件流中,尤其是当它变得越来越大时...
另一种情况是“三进制分配”,

fn add_one(x: i32, y: i32, s: i32) -> i32 {
    if s == 1 { x + 1 } else { y + 1 }
}

fn main() {
    println!("{:#?}", add_one(1, 2, 1));
}

bool 中更有意义,

fn assign_value(s: bool) -> i32 {
    if s { 5 } else { -5 }
}

fn main() {
    println!("{:#?}", assign_value(false));
}

./target/debug/mytest
-5

总之,我们最好不要使用 return,而是要明确函数的类型

rhfm7lfc

rhfm7lfc6#

我对下面的解决方案非常满意:配置Clippy不为不必要的返回发出警告,并在缺少最终返回时警告我。
为此,只需在根文件的顶部添加:“”main.rs“”或"“lib.rs”“执行以下剪辑lint:

#![deny(clippy::implicit_return)]
#![allow(clippy::needless_return)]

对我们来说,额外的好处是,在一些闭包中,它可以防止rustfmt删除大括号。在我的团队中,一致认为在闭包中使用大括号会更好,从而在函数参数和代码之间增加额外的分隔。在下面的示例中:

#[derive(Clone)]
struct User {
    username: String,
}

impl User {
    pub fn new(username: String) -> User {
        return User { username };
    }

    // Code before the pass of rustfmt
    pub fn get_in(&self, users: Vec<User>) -> Option<User> {
        users.into_iter().find(|user| {
            user.username == self.username
        })
    }
}

在添加lint之前,rustfm会将get_in方法转换为:

// Code after the pass of rustfmt
pub fn get_in(&self, users: Vec<User>) -> Option<User> {
   users.into_iter().find(|user| user.username == self.username)
}

现在,我们得到:

pub fn get_in(&self, users: Vec<User>) -> Option<User> {
       return users.into_iter().find(|user| {
           return user.username == self.username;
       });
    }

这当然是一个品味问题,但我们在我的团队中一致倾向于后者,特别是因为存在或不存在一个分号(很难看出)不是类型不匹配的原因。因此,我们对该解决方案非常满意(即使有2个return语句增加的冗长)。最终,我们需要另一个lint或rustfm选项来处理我们在大括号方面的特殊口味...

相关问题