Argomenti di funzione predefiniti in Rust


101

È possibile in Rust creare una funzione con un argomento predefinito?

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

4
# 6973 contiene diverse soluzioni (usando una struttura).
huon

Nel 2020, come puoi codificarlo?
puentesdiaz

@puentesdias La risposta accettata è ancora la risposta corretta. Non c'è modo di farlo in Rust e devi scrivere una macro o usare Optione passare esplicitamente None.
Jeroen,

Risposte:


55

No, al momento non lo è. Penso che sia probabile che alla fine verrà implementato, ma al momento non c'è lavoro attivo in questo spazio.

La tecnica tipica qui impiegata consiste nell'usare funzioni o metodi con nomi e firme differenti.


2
@ ner0x652: ma nota che questo approccio è ufficialmente scoraggiato.
Chris Morgan

@ChrisMorgan Hai una fonte per essere ufficialmente scoraggiato?
Jeroen

1
@JeroenBollen Il meglio che posso trovare in un paio di minuti di ricerca è reddit.com/r/rust/comments/556c0g/… , dove ci sono persone come Brson, che all'epoca era il leader del progetto Rust. IRC avrebbe potuto averne di più, non sono sicuro.
Chris Morgan

107

Poiché gli argomenti predefiniti non sono supportati, puoi ottenere un comportamento simile usando Option<T>

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

Questo raggiunge l'obiettivo di avere il valore predefinito e la funzione codificati solo una volta (invece che in ogni chiamata), ma ovviamente è molto di più da digitare. La chiamata alla funzione avrà un aspetto add(None, None), che potrebbe piacerti o meno a seconda della tua prospettiva.

Se vedi che non digita nulla nell'elenco degli argomenti poiché il programmatore si dimentica potenzialmente di fare una scelta, allora il grande vantaggio qui è nell'esplicita '; il chiamante sta dicendo esplicitamente che vuole andare con il tuo valore predefinito e riceverà un errore di compilazione se non inserisce nulla. Consideralo come una digitazione add(DefaultValue, DefaultValue).

Puoi anche usare una 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);

La grande differenza tra le due soluzioni è che con gli argomenti "Opzione" è completamente valido da scrivere add(None, Some(4)), ma con la corrispondenza del modello macro non è possibile (questo è simile alle regole degli argomenti predefinite di Python).

Puoi anche usare una struttura "argomenti" e i tratti 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)));
}

Questa scelta è ovviamente molto più codice, ma a differenza del design macro utilizza il sistema dei tipi, il che significa che gli errori del compilatore saranno più utili per l'utente della libreria / API. Ciò consente inoltre agli utenti di realizzare la propria Fromimplementazione se ciò è loro utile.


2
questa risposta sarebbe migliore come più risposte, una per ogni approccio. Voglio votare solo uno di loro
Joel

56

No, Rust non supporta gli argomenti delle funzioni predefinite. Devi definire metodi diversi con nomi diversi. Non esiste nemmeno il sovraccarico di funzioni, perché Rust usa i nomi delle funzioni per derivare i tipi (il sovraccarico di funzioni richiede l'opposto).

In caso di inizializzazione della struttura è possibile utilizzare la sintassi di aggiornamento della struttura in questo modo:

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

[su richiesta, ho incrociato questa risposta da una domanda duplicata]


4
Questo è un modello molto utilizzabile per gli argomenti predefiniti. Dovrebbe essere più in alto
Ben

9

Rust non supporta gli argomenti delle funzioni predefinite e non credo che verrà implementato in futuro. Quindi ho scritto un duang proc_macro per implementarlo nella forma macro.

Per esempio:

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

Se stai usando Rust 1.12 o successivo, puoi almeno rendere gli argomenti delle funzioni più facili da usare con Optione 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);
}

7
Sebbene tecnicamente accurato, la comunità di Rust è apertamente divisa sul fatto che questa sia o meno una "buona" idea. Personalmente cado nel campo dei "non buoni".
Shepmaster

1
@Shepmaster può eventualmente aumentare la dimensione del codice e non è super leggibile. Sono queste le obiezioni all'uso di quel modello? Finora ho trovato che i compromessi siano utili al servizio di API ergonomiche, ma considererei che potrei perdere altri trucchi.
Squidpickles

2

Un altro modo potrebbe essere quello di dichiarare un'enumerazione con i parametri opzionali come varianti, che possono essere parametrizzati per prendere il tipo giusto per ciascuna opzione. La funzione può essere implementata per prendere una fetta di lunghezza variabile delle varianti enum. Possono essere in qualsiasi ordine e lunghezza. I valori predefiniti vengono implementati all'interno della funzione come assegnazioni iniziali.

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

}

produzione:

  name: Bob
weight: 90 kg
height: 1.8 m
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.