Đối số hàm mặc định trong Rust


100

Trong Rust có thể tạo một hàm với một đối số mặc định không?

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

4
# 6973 chứa một số cách giải quyết (sử dụng cấu trúc).
huon

Vào năm 2020, bạn có thể viết mã nó như thế nào?
puentesdiaz

@puentesdias Câu trả lời được chấp nhận vẫn là câu trả lời đúng. Không có cách nào để làm điều đó trong Rust, và bạn phải viết macro hoặc sử dụng Optionvà chuyển một cách rõ ràng None.
Jeroen

Câu trả lời:


55

Không, nó không phải là hiện tại. Tôi nghĩ rằng nó có khả năng cuối cùng sẽ được thực hiện, nhưng không có hoạt động tích cực nào trong không gian này hiện tại.

Kỹ thuật điển hình được sử dụng ở đây là sử dụng các hàm hoặc phương thức có tên và chữ ký khác nhau.


2
@ ner0x652: nhưng lưu ý rằng cách tiếp cận đó chính thức không được khuyến khích.
Chris Morgan

@ChrisMorgan Bạn có nguồn nào để chính thức không khuyến khích không?
Jeroen

1
@JeroenBollen Điều tốt nhất tôi có thể nghĩ ra trong vài phút 'tìm kiếm là reddit.com/r/rust/comments/556c0g/… , nơi bạn có những người như brson, người lãnh đạo dự án Rust vào thời điểm đó. IRC có thể có nhiều hơn, không chắc chắn.
Chris Morgan

107

Vì các đối số mặc định không được hỗ trợ, bạn có thể nhận được một hành vi tương tự bằng cách sử dụng Option<T>

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

Điều này hoàn thành mục tiêu của việc có giá trị mặc định và hàm chỉ được mã hóa một lần (thay vì trong mỗi lần gọi), nhưng tất nhiên là nhiều hơn thế nữa để nhập. Lệnh gọi hàm sẽ trông như thế nào add(None, None), bạn có thể thích hoặc không tùy theo quan điểm của bạn.

Nếu bạn không thấy nhập gì trong danh sách đối số vì người viết mã có khả năng quên lựa chọn thì lợi thế lớn ở đây là tính rõ ràng; người gọi đang nói rõ ràng rằng họ muốn sử dụng giá trị mặc định của bạn và sẽ gặp lỗi biên dịch nếu họ không đặt gì. Hãy nghĩ về nó như đánh máy add(DefaultValue, DefaultValue).

Bạn cũng có thể sử dụng macro:

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);

Sự khác biệt lớn giữa hai giải pháp là với các đối số -al của "Option" hoàn toàn hợp lệ để viết add(None, Some(4)), nhưng với mẫu macro phù hợp thì bạn không thể (điều này tương tự như các quy tắc đối số mặc định của Python).

Bạn cũng có thể sử dụng cấu trúc "đối số" và From/ Intođặc điểm:

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)));
}

Sự lựa chọn này rõ ràng là nhiều mã hơn, nhưng không giống như thiết kế macro, nó sử dụng hệ thống loại có nghĩa là các lỗi trình biên dịch sẽ hữu ích hơn cho người dùng thư viện / API của bạn. Điều này cũng cho phép người dùng Fromthực hiện triển khai của riêng họ nếu điều đó hữu ích cho họ.


2
câu trả lời này sẽ tốt hơn dưới dạng một số câu trả lời, một câu trả lời cho mỗi cách tiếp cận. tôi muốn upvote chỉ là một trong số họ
joel

56

Không, Rust không hỗ trợ các đối số hàm mặc định. Bạn phải xác định các phương thức khác nhau với các tên khác nhau. Không có chức năng quá tải, bởi vì Rust sử dụng tên chức năng để dẫn xuất các loại (chức năng quá tải yêu cầu ngược lại).

Trong trường hợp khởi tạo cấu trúc, bạn có thể sử dụng cú pháp cập nhật cấu trúc như sau:

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);
}

[theo yêu cầu, tôi đã đăng chéo câu trả lời này từ một câu hỏi trùng lặp]


4
Đây là một mẫu rất có thể sử dụng cho các đối số mặc định. Nên cao hơn
Ben

9

Rust không hỗ trợ các đối số hàm mặc định và tôi không tin rằng nó sẽ được triển khai trong tương lai. Vì vậy, tôi đã viết một proc_macro duang để triển khai nó ở dạng macro.

Ví dụ:

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);
}

7

Nếu bạn đang sử dụng Rust 1.12 trở lên, ít nhất bạn có thể làm cho các đối số hàm dễ sử dụng hơn với 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);
}

7
Mặc dù chính xác về mặt kỹ thuật, nhưng cộng đồng Rust vẫn bị chia rẽ về việc liệu đây có phải là một ý tưởng "tốt" hay không. Cá nhân tôi rơi vào trại "không tốt".
Shepmaster

1
@Shepmaster nó có thể tăng kích thước mã và nó không thể đọc được. Đó có phải là những phản đối việc sử dụng mô hình đó? Cho đến nay, tôi nhận thấy sự đánh đổi là đáng giá để phục vụ cho các API công thái học, nhưng sẽ cân nhắc rằng tôi có thể thiếu một số điều cần biết khác.
inkpickles

2

Một cách khác có thể là khai báo một enum với các tham số tùy chọn dưới dạng các biến thể, có thể được tham số hóa để lấy đúng loại cho mỗi tùy chọn. Hàm có thể được triển khai để lấy một lát cắt có độ dài thay đổi của các biến thể enum. Chúng có thể theo thứ tự và độ dài bất kỳ. Các giá trị mặc định được thực hiện trong hàm dưới dạng các nhiệm vụ ban đầu.

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") ] );

}

đầu ra:

  name: Bob
weight: 90 kg
height: 1.8 m
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.