Standardfunktionsargumente in Rust


94

Ist es in Rust möglich, eine Funktion mit einem Standardargument zu erstellen?

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

4
# 6973 enthält mehrere Workarounds (unter Verwendung einer Struktur).
Huon

Wie können Sie es im Jahr 2020 codieren?
Puentesdiaz

@puentesdias Die akzeptierte Antwort ist immer noch die richtige Antwort. In Rust gibt es keine Möglichkeit, dies zu tun, und Sie müssen entweder ein Makro schreiben oder es verwenden Optionund explizit übergeben None.
Jeroen

Antworten:


54

Nein, das ist derzeit nicht der Fall. Ich halte es für wahrscheinlich, dass es irgendwann implementiert wird, aber es gibt derzeit keine aktive Arbeit in diesem Bereich.

Die hier verwendete typische Technik besteht darin, Funktionen oder Methoden mit unterschiedlichen Namen und Signaturen zu verwenden.


2
@ ner0x652: aber beachte, dass dieser Ansatz offiziell nicht empfohlen wird.
Chris Morgan

@ChrisMorgan Haben Sie eine Quelle dafür, von der offiziell abgeraten wird?
Jeroen

1
@JeroenBollen Das Beste, was ich in ein paar Minuten finden kann, ist reddit.com/r/rust/comments/556c0g/… , wo Sie Leute wie brson haben, die zu dieser Zeit der Rust-Projektleiter waren. IRC könnte mehr gehabt haben, nicht sicher.
Chris Morgan

97

Da Standardargumente nicht unterstützt werden, können Sie mit ein ähnliches Verhalten erzielen Option<T>

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

Dies erreicht das Ziel, den Standardwert und die Funktion nur einmal (statt bei jedem Aufruf) codieren zu lassen, ist aber natürlich noch viel mehr zu tippen. Der Funktionsaufruf sieht so aus add(None, None), wie Sie es je nach Perspektive mögen oder nicht.

Wenn Sie sehen, dass Sie nichts in die Argumentliste eingeben, da der Codierer möglicherweise vergisst, eine Auswahl zu treffen, liegt der große Vorteil hier in der expliziten Aussage. Der Aufrufer sagt ausdrücklich, dass er mit Ihrem Standardwert arbeiten möchte, und erhält einen Kompilierungsfehler, wenn er nichts einfügt. Betrachten Sie es als Tippen add(DefaultValue, DefaultValue).

Sie können auch ein Makro verwenden:

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

Der große Unterschied zwischen den beiden Lösungen besteht darin, dass das Schreiben mit "Option" -Al-Argumenten vollständig gültig add(None, Some(4))ist, mit dem Makromuster-Matching jedoch nicht (dies ähnelt den Standardargumentregeln von Python).

Sie können auch eine "Argumente" -Struktur und die From/ IntoMerkmale verwenden:

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

Diese Auswahl ist offensichtlich viel mehr Code, aber im Gegensatz zum Makrodesign wird das Typsystem verwendet, was bedeutet, dass die Compilerfehler für Ihren Bibliotheks- / API-Benutzer hilfreicher sind. Auf diese Weise können Benutzer auch ihre eigene FromImplementierung vornehmen , wenn dies für sie hilfreich ist.


1
Diese Antwort wäre besser als mehrere Antworten, eine für jeden Ansatz. Ich möchte nur einen von ihnen
positiv bewerten

50

Nein, Rust unterstützt keine Standardfunktionsargumente. Sie müssen verschiedene Methoden mit unterschiedlichen Namen definieren. Es gibt auch keine Funktionsüberladung, da Rust Funktionsnamen verwendet, um Typen abzuleiten (Funktionsüberladung erfordert das Gegenteil).

Im Falle einer Strukturinitialisierung können Sie die Strukturaktualisierungssyntax wie folgt verwenden:

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

[Auf Anfrage habe ich diese Antwort von einer doppelten Frage abgekreuzt]


3
Dies ist ein sehr brauchbares Muster für Standardargumente. Sollte höher sein
Ben

7

Wenn Sie mit Rust 1.12 oder höher verwenden , können Sie zumindest Schliesser Argumente einfacher zu bedienen mit Optionund 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);
}

6
Obwohl technisch korrekt, ist die Rust-Community stimmlich gespalten darüber, ob dies eine "gute" Idee ist oder nicht. Ich persönlich falle in das "nicht gute" Lager.
Shepmaster

1
@ Shepmaster kann möglicherweise die Codegröße erhöhen, und es ist nicht super lesbar. Sind das die Einwände gegen die Verwendung dieses Musters? Ich habe bisher festgestellt, dass sich die Kompromisse im Hinblick auf ergonomische APIs lohnen, würde aber bedenken, dass mir möglicherweise einige andere Fallstricke fehlen.
Squidpickles

7

Rust unterstützt keine Standardfunktionsargumente und ich glaube nicht, dass es in Zukunft implementiert wird. Also habe ich ein proc_macro duang geschrieben , um es in der Makroform zu implementieren.

Beispielsweise:

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

2

Eine andere Möglichkeit könnte darin bestehen, eine Aufzählung mit den optionalen Parametern als Varianten zu deklarieren, die so parametrisiert werden können, dass für jede Option der richtige Typ verwendet wird. Die Funktion kann implementiert werden, um einen Slice variabler Länge der Enum-Varianten zu nehmen. Sie können in beliebiger Reihenfolge und Länge sein. Die Standardeinstellungen werden innerhalb der Funktion als Erstzuweisungen implementiert.

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

}

Ausgabe:

  name: Bob
weight: 90 kg
height: 1.8 m
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.