Argumentos de função padrão em Rust

98

É possível no Rust criar uma função com um argumento padrão?

fn add(a: int = 1, b: int = 2) { a + b }
Jeroen
fonte
4
# 6973 contém várias soluções alternativas (usando uma estrutura).
huon
Em 2020, como você pode codificá-lo?
puentesdiaz
@puentesdias A resposta aceita ainda é a resposta correta. Não há como fazer isso no Rust, e você deve escrever uma macro ou usar Optione passar explicitamente None.
Jeroen

Respostas:

55

Não, não é no momento. Eu acho que provavelmente será implementado, mas não há nenhum trabalho ativo neste espaço no momento.

A técnica típica empregada aqui é usar funções ou métodos com nomes e assinaturas diferentes.

Chris Morgan
fonte
2
@ ner0x652: mas observe que essa abordagem é oficialmente desencorajada.
Chris Morgan
@ChrisMorgan Você tem uma fonte para isso estar oficialmente desencorajado?
Jeroen de
1
@JeroenBollen O melhor que posso encontrar em alguns minutos de pesquisa é reddit.com/r/rust/comments/556c0g/… , onde você tem pessoas como brson, que era o líder do projeto Rust na época. O IRC pode ter tido mais, não tenho certeza.
Chris Morgan
100

Uma vez que os argumentos padrão não são suportados, você pode obter um comportamento semelhante usando Option<T>

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

Isso cumpre o objetivo de ter o valor padrão e a função codificados apenas uma vez (em vez de em cada chamada), mas é claro que é muito mais para digitar. A chamada de função será semelhante aadd(None, None) , da qual você pode ou não gostar, dependendo da sua perspectiva.

Se você não perceber nada digitando na lista de argumentos, já que o codificador potencialmente esquece de fazer uma escolha, a grande vantagem aqui está na explicitação; o chamador está dizendo explicitamente que deseja seguir seu valor padrão e obterá um erro de compilação se não inserir nada. Pense nisso como uma digitação add(DefaultValue, DefaultValue).

Você também pode usar uma 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);

A grande diferença entre as duas soluções é que com os argumentos -al "Opção" é completamente válido escrever add(None, Some(4)) , mas com a correspondência de padrão de macro você não pode (isso é semelhante às regras de argumento padrão do Python).

Você também pode usar uma estrutura de "argumentos" e os From/ Intotraits:

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 escolha é obviamente muito mais código, mas ao contrário do design de macro, ela usa o sistema de tipos, o que significa que os erros do compilador serão mais úteis para o usuário de sua biblioteca / API. Isso também permite que os usuários façam sua própria Fromimplementação, se isso for útil para eles.

ampron
fonte
2
esta resposta seria melhor como várias respostas, uma para cada abordagem. Eu quero votar a favor de apenas um deles
joel
52

Não, Rust não oferece suporte a argumentos de função padrão. Você deve definir métodos diferentes com nomes diferentes. Também não há sobrecarga de função, porque Rust usa nomes de função para derivar tipos (a sobrecarga de função requer o oposto).

No caso de inicialização da estrutura, você pode usar a sintaxe de atualização da estrutura como esta:

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, fiz uma postagem cruzada desta resposta de uma pergunta duplicada]

eulerdisk
fonte
4
Este é um padrão muito útil para argumentos padrão. Deve ser mais alto
Ben,
7

Se você estiver usando Rust 1.12 ou posterior, você pode pelo menos tornar os argumentos de função mais fáceis de usar com 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);
}
lulas
fonte
6
Embora tecnicamente precisa, a comunidade Rust está vocalmente dividida sobre se essa é ou não uma "boa" ideia. Eu, pessoalmente, caio no campo "não é bom".
Shepmaster
1
@Shepmaster pode possivelmente aumentar o tamanho do código e não é super legível. Essas são as objeções ao uso desse padrão? Até agora, descobri que as compensações valem a pena no serviço de APIs ergonômicas, mas consideraria que posso estar perdendo alguns outros truques.
squidpickles de
7

Rust não suporta argumentos de função padrão, e não acredito que será implementado no futuro. Então, escrevi um proc_macro duang para implementá-lo na forma de macro.

Por exemplo:

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
fonte
2

Outra forma poderia ser declarar um enum com os parâmetros opcionais como variantes, que podem ser parametrizados para obter o tipo certo para cada opção. A função pode ser implementada para obter uma fatia de comprimento variável das variantes enum. Eles podem estar em qualquer ordem e comprimento. Os padrões são implementados na função como atribuições iniciais.

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

}

resultado:

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