Argumen fungsi default di Rust

111

Apakah mungkin untuk membuat fungsi dengan argumen default?

fn add(a: int = 1, b: int = 2) { a + b }
Jeroen
sumber
4
# 6973 berisi beberapa solusi (menggunakan struct).
huon
Pada tahun 2020, bagaimana Anda dapat mengkodekannya?
puentesdiaz
@puentesdias Jawaban yang diterima masih merupakan jawaban yang benar. Tidak ada cara untuk melakukannya di Rust, dan Anda harus menulis makro, atau menggunakan Optiondan secara eksplisit meneruskan None.
Jeroen

Jawaban:

58

Tidak, saat ini tidak. Saya pikir kemungkinan itu pada akhirnya akan diterapkan, tetapi tidak ada pekerjaan aktif di ruang ini saat ini.

Teknik khas yang digunakan di sini adalah menggunakan fungsi atau metode dengan nama dan tanda tangan yang berbeda.

Chris Morgan
sumber
2
@ ner0x652: tetapi perhatikan bahwa pendekatan itu secara resmi tidak disarankan.
Chris Morgan
@ChrisMorgan Apakah Anda memiliki sumber yang secara resmi tidak disarankan?
Jeroen
1
@JeroenBollen Pencarian terbaik yang bisa saya dapatkan dalam beberapa menit adalah reddit.com/r/rust/comments/556c0g/… , di mana Anda memiliki orang-orang seperti brson yang merupakan pemimpin proyek Rust pada saat itu. IRC mungkin memiliki lebih banyak, tidak yakin.
Chris Morgan
115

Karena argumen default tidak didukung, Anda bisa mendapatkan perilaku serupa menggunakan Option<T>

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

Ini menyelesaikan tujuan untuk memiliki nilai default dan fungsi yang dikodekan hanya sekali (bukan di setiap panggilan), tetapi tentu saja lebih banyak untuk diketik. Panggilan fungsi akan terlihat seperti add(None, None), yang mungkin Anda suka atau tidak suka tergantung pada perspektif Anda.

Jika Anda tidak melihat ketikan apa pun dalam daftar argumen karena pembuat kode berpotensi lupa membuat pilihan, maka keuntungan besar di sini adalah secara eksplisit; pemanggil secara eksplisit mengatakan mereka ingin menggunakan nilai default Anda, dan akan mendapatkan kesalahan kompilasi jika mereka tidak memasukkan apa pun. Anggap saja sebagai mengetik add(DefaultValue, DefaultValue).

Anda juga bisa menggunakan makro:

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

Perbedaan besar antara kedua solusi tersebut adalah bahwa dengan argumen "Option" -al itu benar-benar valid untuk ditulis add(None, Some(4)), tetapi dengan pola makro yang cocok Anda tidak bisa (ini mirip dengan aturan argumen default Python).

Anda juga bisa menggunakan struct "argumen" dan From/ Intosifat:

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

Pilihan ini jelas lebih banyak kode, tetapi tidak seperti desain makro, ia menggunakan sistem tipe yang berarti kesalahan kompilator akan lebih membantu pengguna perpustakaan / API Anda. Ini juga memungkinkan pengguna untuk membuat Fromimplementasi mereka sendiri jika itu berguna bagi mereka.

ampron
sumber
3
jawaban ini akan lebih baik sebagai beberapa jawaban, satu untuk setiap pendekatan. saya ingin memberi suara positif pada salah satunya
joel
60

Tidak, Rust tidak mendukung argumen fungsi default. Anda harus menentukan metode berbeda dengan nama berbeda. Tidak ada fungsi yang berlebihan juga, karena Rust menggunakan nama fungsi untuk mendapatkan tipe (kelebihan beban membutuhkan yang sebaliknya).

Dalam kasus inisialisasi struct Anda dapat menggunakan sintaks pembaruan struct seperti ini:

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

[atas permintaan, saya mem-posting silang jawaban ini dari pertanyaan duplikat]

eulerdisk.dll
sumber
5
Ini adalah pola yang sangat bisa digunakan untuk argumen default. Harus lebih tinggi
Ben
10

Rust tidak mendukung argumen fungsi default, dan saya tidak yakin ini akan diterapkan di masa mendatang. Jadi saya menulis duang proc_macro untuk menerapkannya dalam bentuk makro.

Sebagai contoh:

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);
}
zhengmian hu
sumber
7

Jika Anda menggunakan Rust 1.12 atau yang lebih baru, Anda setidaknya dapat membuat argumen fungsi lebih mudah digunakan dengan Optiondan 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);
}
cumi-cumi
sumber
7
Meskipun secara teknis akurat, komunitas Rust secara vokal berbeda pendapat tentang apakah ini adalah ide yang "baik" atau tidak. Saya pribadi termasuk dalam kamp yang "tidak baik".
Shepmaster
1
@Shepmaster itu mungkin dapat meningkatkan ukuran kode, dan itu tidak super dapat dibaca. Apakah itu keberatan menggunakan pola itu? Sejauh ini saya telah menemukan trade-off yang bermanfaat dalam layanan API ergonomis, tetapi akan menganggap bahwa saya mungkin kehilangan beberapa gotcha lainnya.
squidpickles
2

Cara lain adalah dengan mendeklarasikan enum dengan parameter opsional sebagai varian, yang dapat dijadikan parameter untuk mengambil jenis yang tepat untuk setiap opsi. Fungsi ini dapat diterapkan untuk mengambil potongan panjang variabel dari varian enum. Mereka bisa dalam urutan dan panjang apapun. Default diimplementasikan dalam fungsi sebagai tugas awal.

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

}

keluaran:

  name: Bob
weight: 90 kg
height: 1.8 m
Todd
sumber