Аргументы функции по умолчанию в Rust

102

Возможно ли в Rust создать функцию с аргументом по умолчанию?

fn add(a: int = 1, b: int = 2) { a + b }
Jeroen
источник
4
# 6973 содержит несколько обходных путей (с использованием структуры).
huon 05
Как его кодировать в 2020 году?
puentesdiaz
@puentesdias Принятый ответ по-прежнему является правильным. В Rust нет возможности сделать это, и вам нужно либо написать макрос, либо использовать Optionи явно передать None.
Jeroen

Ответы:

55

Нет, в настоящее время нет. Думаю, что в конечном итоге это будет реализовано, но в настоящее время активной работы в этом направлении нет.

Типичный метод, используемый здесь, заключается в использовании функций или методов с разными именами и сигнатурами.

Крис Морган
источник
2
@ ner0x652: но учтите, что такой подход официально не приветствуется.
Крис Морган
@ChrisMorgan. Есть ли у вас источник, из-за которого это официально не одобряют?
Jeroen
1
@JeroenBollen Лучшее, что я могу найти за пару минут поиска, - это reddit.com/r/rust/comments/556c0g/… , где есть такие люди, как Брсон, который в то время был руководителем проекта Rust. У IRC могло быть больше, не уверен.
Крис Морган
108

Поскольку аргументы по умолчанию не поддерживаются, вы можете получить аналогичное поведение, используя Option<T>

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

Таким образом достигается цель, заключающаяся в том, что значение по умолчанию и функция кодируются только один раз (а не при каждом вызове), но, конечно, гораздо больше нужно вводить. Вызов функции будет выглядеть так add(None, None), что вам может понравиться , а может и не понравиться, в зависимости от вашей точки зрения.

Если вы видите, что в списке аргументов ничего не набираете, поскольку кодировщик потенциально забывает сделать выбор, то здесь большое преимущество заключается в ясности; вызывающий явно говорит, что хочет использовать ваше значение по умолчанию, и получит ошибку компиляции, если ничего не введет. Думайте об этом как о печатании add(DefaultValue, DefaultValue).

Вы также можете использовать макрос:

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

Большая разница между этими двумя решениями заключается в том, что с аргументами «Option» -al запись полностью допустима add(None, Some(4)), но с сопоставлением с шаблоном макроса вы не можете (это похоже на правила аргументов Python по умолчанию).

Вы также можете использовать структуру «arguments» и свойства 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)));
}

Этот выбор, очевидно, представляет собой намного больше кода, но в отличие от дизайна макроса он использует систему типов, что означает, что ошибки компилятора будут более полезны для вашей библиотеки / пользователя API. Это также позволяет пользователям создавать свои собственные Fromреализации, если это им полезно.

ампрон
источник
2
этот ответ был бы лучше в виде нескольких ответов, по одному для каждого подхода. я хочу upvote только один из них
Joel
56

Нет, Rust не поддерживает аргументы функций по умолчанию. Вы должны определять разные методы с разными именами. Также нет перегрузки функций, потому что Rust использует имена функций для получения типов (перегрузка функций требует обратного).

В случае инициализации структуры вы можете использовать синтаксис обновления структуры следующим образом:

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

[по запросу я отправил этот ответ из дублированного вопроса]

eulerdisk
источник
4
Это очень полезный шаблон для аргументов по умолчанию. Должно быть выше
Бен
9

Rust не поддерживает аргументы функций по умолчанию, и я не верю, что это будет реализовано в будущем. Поэтому я написал дуанг proc_macro, чтобы реализовать его в форме макроса.

Например:

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

Если вы используете Rust 1.12 или новее, вы можете, по крайней мере, упростить использование аргументов функции с помощью Optionи 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
Несмотря на то, что с технической точки зрения это точно, сообщество Rust разделяет мнение о том, является ли это «хорошей» идеей. Я лично попадаю в лагерь «нехороших».
Shepmaster
1
@Shepmaster это может увеличить размер кода, и это не очень читабельно. Это возражения против использования этого шаблона? Я до сих пор считаю, что компромиссы оправдывают себя при обслуживании эргономичных API-интерфейсов, но считаю, что мне могут не хватать некоторых других подводных камней.
Squidpickles
2

Другой способ - объявить перечисление с необязательными параметрами как варианты, которые можно параметризовать, чтобы выбрать правильный тип для каждой опции. Функцию можно реализовать для получения фрагмента переменной длины из вариантов перечисления. Они могут быть любого порядка и длины. Значения по умолчанию реализованы в функции как начальные назначения.

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

}

выход:

  name: Bob
weight: 90 kg
height: 1.8 m
Тодд
источник