【问题标题】:Default function arguments in RustRust 中的默认函数参数
【发布时间】:2014-07-25 17:00:06
【问题描述】:

是否可以使用默认参数创建函数?

fn add(a: int = 1, b: int = 2) { a + b }

【问题讨论】:

  • #6973 包含几个变通方法(使用结构)。
  • 2020年,怎么码字?
  • @puentesdias 接受的答案仍然是正确的答案。在 Rust 中没有办法做到这一点,您必须编写宏,或者使用 Option 并显式传递 None

标签: function parameters arguments rust


【解决方案1】:

不,目前还没有。我认为它最终可能会实施,但目前在这个领域没有积极的工作。

这里采用的典型技术是使用具有不同名称和签名的函数或方法。

【讨论】:

  • @ner0x652:但请注意,官方不鼓励这种方法。
  • @ChrisMorgan 你有官方不鼓励的消息来源吗?
  • @JeroenBollen 在几分钟的搜索中,我能想到的最好的结果是 reddit.com/r/rust/comments/556c0g/…,在那里你可以找到像 brson 这样的人,他当时是 Rust 项目的负责人。 IRC 可能有更多,不确定。
  • “我认为它很可能最终会实施” - 为什么?它不会增加额外的运行时开销吗?如果要添加 rust,这似乎与整个“零成本抽象”理念背道而驰。
  • @DylanKerler 他们可以做一些类似于单态化的事情,这只会增加编译时开销
【解决方案2】:

不,Rust 不支持默认函数参数。您必须使用不同的名称定义不同的方法。也没有函数重载,因为 Rust 使用函数名来派生类型(函数重载需要相反)。

在结构初始化的情况下,您可以像这样使用结构更新语法:

use std::default::Default;

#[derive(Debug)]
pub struct Sample {
    a: u32,
    b: u32,
    c: u32,
}

impl Default for Sample {
    fn default() -> Self {
        Sample { a: 2, b: 4, c: 6}
    }
}

fn main() {
    let s = Sample { c: 23, ..Sample::default() };
    println!("{:?}", s);
}

[根据要求,我从一个重复的问题中交叉发布了这个答案]

【讨论】:

    【解决方案3】:

    由于不支持默认参数,您可以使用 Option<T> 获得类似的行为

    fn add(a: Option<i32>, b: Option<i32>) -> i32 {
        a.unwrap_or(1) + b.unwrap_or(2)
    }
    

    这实现了将默认值和函数只编码一次(而不是在每次调用中)的目标,但当然要输入更多内容。函数调用看起来像add(None, None),你可能喜欢也可能不喜欢,这取决于你的观点。

    如果您看到在参数列表中没有输入任何内容,因为编码员可能会忘记做出选择,那么这里最大的优势在于明确性;调用者明确表示他们想使用您的默认值,如果他们什么都不放,将会得到编译错误。把它想象成输入add(DefaultValue, DefaultValue)

    你也可以使用宏:

    fn add(a: i32, b: i32) -> i32 {
        a + b
    }
    
    macro_rules! add {
        ($a: expr) => {
            add($a, 2)
        };
        () => {
            add(1, 2)
        };
    }
    
    assert_eq!(add!(), 3);
    assert_eq!(add!(4), 6);
    

    两种解决方案的最大区别在于,使用“Option”-al 参数时,写 add(None, Some(4)) 完全有效,但使用宏模式匹配则不能(这类似于 Python 的默认参数规则)。

    您还可以使用“参数”结构和 From/Into 特征:

    pub struct FooArgs {
        a: f64,
        b: i32,
    }
    
    impl Default for FooArgs {
        fn default() -> Self {
            FooArgs { a: 1.0, b: 1 }
        }
    }
    
    impl From<()> for FooArgs {
        fn from(_: ()) -> Self {
            Self::default()
        }
    }
    
    impl From<f64> for FooArgs {
        fn from(a: f64) -> Self {
            Self {
                a: a,
                ..Self::default()
            }
        }
    }
    
    impl From<i32> for FooArgs {
        fn from(b: i32) -> Self {
            Self {
                b: b,
                ..Self::default()
            }
        }
    }
    
    impl From<(f64, i32)> for FooArgs {
        fn from((a, b): (f64, i32)) -> Self {
            Self { a: a, b: b }
        }
    }
    
    pub fn foo<A>(arg_like: A) -> f64
    where
        A: Into<FooArgs>,
    {
        let args = arg_like.into();
        args.a * (args.b as f64)
    }
    
    fn main() {
        println!("{}", foo(()));
        println!("{}", foo(5.0));
        println!("{}", foo(-3));
        println!("{}", foo((2.0, 6)));
    }
    

    这个选择显然需要更多的代码,但与宏设计不同的是,它使用类型系统,这意味着编译器错误将对您的库/API 用户更有帮助。如果这对他们有帮助,这也允许用户制作自己的 From 实现。

    【讨论】:

    • 这个答案最好有几个答案,每种方法一个。我只想对其中一个投赞成票
    • 如果您提到您喜欢哪种方法,您的评论会更有用。 ;-) 我猜是宏
    • 我尝试使用选项和宏方法解决几个问题。宏更容易编写和使用。
    【解决方案4】:

    如果您使用的是 Rust 1.12 或更高版本,您至少可以使函数参数更易于使用 Optioninto()

    fn add<T: Into<Option<u32>>>(a: u32, b: T) -> u32 {
        if let Some(b) = b.into() {
            a + b
        } else {
            a
        }
    }
    
    fn main() {
        assert_eq!(add(3, 4), 7);
        assert_eq!(add(8, None), 8);
    }
    

    【讨论】:

    • 虽然技术上是准确的,但 Rust 社区对于这是否是一个“好”的想法存在分歧。我个人属于“不好”阵营。
    • @Shepmaster 它可能会增加代码大小,而且它不是超级可读的。这些是否反对使用该模式?到目前为止,我发现在为符合人体工程学的 API 服务方面进行权衡是值得的,但我认为我可能会遗漏一些其他问题。
    【解决方案5】:

    Rust 不支持默认函数参数,我不相信它会在未来实现。 于是我写了一个proc_macroduang以宏的形式实现。

    例如:

    duang! ( fn add(a: i32 = 1, b: i32 = 2) -> i32 { a + b } );
    fn main() {
        assert_eq!(add!(b=3, a=4), 7);
        assert_eq!(add!(6), 8);
        assert_eq!(add(4,5), 9);
    }
    

    【讨论】:

      【解决方案6】:

      另一种方法是声明一个带有可选参数的枚举作为变体,可以将其参数化为每个选项采用正确的类型。可以实现该函数以获取枚举变量的可变长度切片。它们可以是任何顺序和长度。默认值在函数内作为初始分配实现。

      enum FooOptions<'a> {
          Height(f64),
          Weight(f64),
          Name(&'a str),
      }
      use FooOptions::*;
      
      fn foo(args: &[FooOptions]) {
          let mut height   = 1.8;
          let mut weight   = 77.11;
          let mut name     = "unspecified".to_string();
          
          for opt in args {
              match opt {
                  Height(h) => height = *h,
                  Weight(w) => weight = *w,
                  Name(n)   => name   =  n.to_string(),
              }
          }
          println!("  name: {}\nweight: {} kg\nheight: {} m", 
                   name, weight, height);
      }
          
      fn main() { 
      
          foo( &[ Weight(90.0), Name("Bob") ] );
      
      }
      

      输出:

        name: Bob
      weight: 90 kg
      height: 1.8 m
      

      args 本身也可以是可选的。

      fn foo(args: Option<&[FooOptions]>) {
          let args = args.or(Some(&[])).unwrap();
          // ...
      }
      

      【讨论】:

      • 我喜欢这个答案,如果您还想让 arg 成为可选参数,您也可以使用 optional 和一些像这样:args: Option
      • @EduardoLuisSantos,好主意。我在这些方面添加了一个示例。谢谢=)
      • 另外我刚刚测试了这种方法(与 Optional 混合)并将函数与一些等效的 python 代码进行比较,Python 平均快 3 倍,很可能是由于这种方法,我仍然更喜欢它比写很多函数但看起来更慢。
      • 我不希望这种将参数传递给函数的方法是最有效的。 Python 会快 3 倍,这有点令人惊讶。我可以看到 PyPy3 快 3 倍,但解释 Python 与 Rust 应用程序的发布版本? @EduardoLuisSantos
      • 以舒适换取性能?
      猜你喜欢
      • 2013-04-15
      • 1970-01-01
      • 2011-02-20
      • 2011-04-09
      • 1970-01-01
      • 1970-01-01
      • 2010-12-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多