是否可以创建一个带有默认参数的函数?
fn add(a: int = 1, b: int = 2) { a + b }
是否可以创建一个带有默认参数的函数?
fn add(a: int = 1, b: int = 2) { a + b }
由于不支持默认参数,您可以使用 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" 类型参数时,完全可以编写 add(None, Some(4))
,但是在使用宏模式匹配时则不行(这类似于Python的默认参数规则)。
您还可以使用 "arguments" 结构以及 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
实现,如果他们觉得有用的话。
目前不支持。我认为它最终可能会被实现,但目前在这个领域没有积极的工作。
这里通常使用具有不同名称和签名的函数或方法的技术。
Option<T>
并通过 .unwrap_or_else(|| …)
或类似方式填充默认值;或者使用 T
并让调用者编写 T::default()
。如果有的话,一流的默认参数值可能会更有效率,因为默认值的填充可以可靠地转移到调用者处,编译器认为这是值得的,而不需要内联整个过程,在提供值的情况下减少工作量 - 虽然通常优化器会使两种方法大致相同。 - Chris Morgan不,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);
}
[应要求,我从重复的问题中转载了这个答案]
Rust不支持默认函数参数,我也不相信它将来会实现。 因此,我编写了一个proc_macro duang,以宏的形式实现它。
例如:
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);
}
Option
和 into()
来使函数参数更易于使用: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);
}
impl Into<Option<>>
来实现。 - Chayim Friedman另一种方法可以是声明一个枚举,将可选参数作为变体,可以对其进行参数化以获取每个选项的正确类型。函数可以实现为接受枚举变体的可变长度切片。它们可以按任何顺序和长度排列。默认值在函数中作为初始赋值实现。
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();
// ...
}
在之前的回答基础上,还要记住您可以创建与现有变量相同名称的新变量,这将隐藏先前的变量。如果您不打算再使用Option<...>
,这对于保持代码清晰很有用。
fn add(a: Option<i32>, b: Option<i32>) -> i32 {
let a = a.unwrap_or(1);
let b = a.unwrap_or(2);
a + b
}
有一个 default_args
crate 可以解决这个问题。
例子:
use default_args::default_args;
default_args! {
fn add(a: int = 1, b: int = 2) {
a + b
}
}
add!(12)
)
问问自己,你为什么想要默认参数?这取决于你的原因,有很多答案:
如果你有太多的参数,最好将代码重构为更多不同的结构体,比如一些构建器模式,也许通过函数记录更新(FRU)。
pub struct MyFunction { ... many arguments ... }
impl Default for MyFunction { ... }
impl MyFunction { go(self) { ... } }
调用方式
MyFunction { desired arguments, ..Default::default() }.go()
你的构建器通常可以是一些相关类型,这使得方法链接更加美观。在这些类型中,你可以在类型级别隐藏参数,假设用户不嵌入你的中间类型。
pub struct MyWorker<R: RngCore = OsRng> { ... }
R: RngCore
参数用于测试向量,以及想要取消随机化签名的利基用户。然而,我希望更广泛的生态系统仅使用良好随机化的签名。我已经采用了 merlin::Transcript 抽象来进行 Fiat-Shamir 变换。因此,我只通过 trait 提供 OsRng
,但是您可以更改 trait 后面的类型以进行测试向量或其他操作。https://github.com/w3f/schnorrkel/blob/master/src/context.rs#L94-L103
///
/// ## Argument
/// should_eat_veg: Option<bool> **default=false**
///
fn plan_my_meal(should_eat_veg: Option<bool>) -> Meal {
let should_eat_veg = should_eat_veg.map_or(false, |v| v);
...
}
Option<T>
类型声明,并将None
作为默认值传递
Option
并显式传递None
。 - Jeroen