Argumentos de función predeterminados en Rust

101

¿Es posible en Rust crear una función con un argumento predeterminado?

fn add(a: int = 1, b: int = 2) { a + b }
Jeroen
fuente
4
# 6973 contiene varias soluciones alternativas (usando una estructura).
huon
En 2020, ¿cómo se puede codificar?
puentesdiaz
@puentesdias La respuesta aceptada sigue siendo la respuesta correcta. No hay forma de hacerlo en Rust, y debe escribir una macro o usar Optiony pasar explícitamente None.
Jeroen

Respuestas:

55

No, no lo es en la actualidad. Creo que es probable que eventualmente se implemente, pero no hay trabajo activo en este espacio en la actualidad.

La técnica típica empleada aquí es utilizar funciones o métodos con diferentes nombres y firmas.

Chris Morgan
fuente
2
@ ner0x652: pero tenga en cuenta que ese enfoque está oficialmente desaconsejado.
Chris Morgan
@ChrisMorgan ¿Tiene una fuente para que se desanime oficialmente?
Jeroen
1
@JeroenBollen Lo mejor que se me ocurre en un par de minutos de búsqueda es reddit.com/r/rust/comments/556c0g/… , donde tienes a gente como brson que era el líder del proyecto Rust en ese momento. IRC podría haber tenido más, no estoy seguro.
Chris Morgan
107

Dado que los argumentos predeterminados no son compatibles, puede obtener un comportamiento similar usando Option<T>

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

Esto logra el objetivo de tener el valor predeterminado y la función codificada solo una vez (en lugar de en cada llamada), pero, por supuesto, hay mucho más para escribir. La llamada a la función se verá así add(None, None), lo que puede que le guste o no según su perspectiva.

Si ve que no escribe nada en la lista de argumentos, ya que el codificador se olvida potencialmente de hacer una elección, entonces la gran ventaja aquí es que es explícito; la persona que llama está diciendo explícitamente que quiere ir con su valor predeterminado y obtendrá un error de compilación si no pone nada. Piense en ello como escribir add(DefaultValue, DefaultValue).

También puedes usar 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 gran diferencia entre las dos soluciones es que con los argumentos de "Opción" -al es completamente válido escribir add(None, Some(4)), pero con el patrón de macro coincidente no puede (esto es similar a las reglas de argumentos predeterminadas de Python).

También puedes usar una estructura de "argumentos" y los rasgos 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)));
}

Esta elección es obviamente mucho más código, pero a diferencia del diseño de macros, utiliza el sistema de tipos, lo que significa que los errores del compilador serán más útiles para el usuario de la biblioteca / API. Esto también permite a los usuarios realizar su propia Fromimplementación si les resulta útil.

ampron
fuente
2
esta respuesta sería mejor como varias respuestas, una para cada enfoque. solo quiero votar a favor de uno de ellos
joel
56

No, Rust no admite argumentos de función predeterminados. Tienes que definir diferentes métodos con diferentes nombres. Tampoco hay sobrecarga de funciones, porque Rust usa nombres de funciones para derivar tipos (la sobrecarga de funciones requiere lo contrario).

En caso de inicialización de la estructura, puede usar la sintaxis de actualización de la estructura de la siguiente manera:

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

[a pedido, publiqué esta respuesta de una pregunta duplicada]

eulerdisk
fuente
4
Este es un patrón muy útil para argumentos predeterminados. Debería estar más arriba
Ben
9

Rust no admite argumentos de función predeterminados y no creo que se implemente en el futuro. Así que escribí un proc_macro duang para implementarlo en forma de macro.

Por ejemplo:

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
fuente
7

Si está utilizando Rust 1.12 o posterior, al menos puede hacer que los argumentos de función sean más fáciles de usar con Optiony 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);
}
calamares
fuente
7
Si bien es técnicamente precisa, la comunidad de Rust está dividida verbalmente sobre si esta es una "buena" idea o no. Yo personalmente caigo en el campo de lo "no bueno".
Shepmaster
1
@Shepmaster posiblemente puede aumentar el tamaño del código y no es súper legible. ¿Son esas las objeciones a usar ese patrón? Hasta ahora he encontrado que las compensaciones valen la pena al servicio de las API ergonómicas, pero consideraría que podría estar perdiendo algunos otros errores.
squidpickles
2

Otra forma podría ser declarar una enumeración con los parámetros opcionales como variantes, que se pueden parametrizar para tomar el tipo correcto para cada opción. La función se puede implementar para tomar un segmento de longitud variable de las variantes de enumeración. Pueden estar en cualquier orden y longitud. Los valores predeterminados se implementan dentro de la función como asignaciones iniciales.

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

}

salida:

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