Аргументи функції за замовчуванням у Rust


100

Чи можливо в Rust створити функцію з аргументом за замовчуванням?

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

4
№ 6973 містить кілька методів роботи (за допомогою структури).
huon

У 2020 році, як ви можете його кодувати?
puentesdiaz

@puentesdias Прийнята відповідь як і раніше є правильною. Це неможливо зробити в Rust, і вам потрібно або написати макрос, або використовувати Optionі явно передати None.
Ієрун

Відповіді:


55

Ні, зараз цього немає. Я думаю, що цілком ймовірно, що це врешті-решт буде впроваджено, але наразі в цьому просторі немає активної роботи.

Типовий прийом, що застосовується тут, полягає у використанні функцій або методів з різними іменами та підписами.


2
@ ner0x652: але зауважте, що такий підхід офіційно не рекомендується.
Кріс Морган,

@ChrisMorgan У вас є джерело для цього офіційно знеохочувати?
Jeroen

1
@JeroenBollen Найкраще, що я можу придумати за кілька хвилин пошуку - це reddit.com/r/rust/comments/556c0g/… , де у вас є такі люди, як brson, який на той час був керівником проекту Rust. IRC міг мати більше, не впевнений.
Chris Morgan

107

Оскільки аргументи за замовчуванням не підтримуються, ви можете отримати подібну поведінку за допомогою 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" цілком вірно писати add(None, Some(4)), але з відповідністю шаблону макросу ви не можете (це схоже на правила аргументів Python за замовчуванням).

Ви також можете використовувати структуру "аргументи" та 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)));
}

Цей вибір, очевидно, набагато більше коду, але на відміну від дизайну макросів він використовує систему типів, що означає, що помилки компілятора будуть більш корисними для вашої бібліотеки / користувача API. Це також дозволяє користувачам здійснювати власну Fromреалізацію, якщо це їм корисно.


2
ця відповідь була б кращою, як кілька відповідей, по одній для кожного підходу. я хочу проголосувати лише за одного
Джоель,

56

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

У випадку ініціалізації структури ви можете використовувати синтаксис оновлення struct таким чином:

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

[на запит, я відповів на відповідь із дубльованого запитання]


4
Це дуже корисний шаблон для аргументів за замовчуванням. Має бути вище
Бен,

9

Rust не підтримує аргументи функції за замовчуванням, і я не вірю, що це буде реалізовано в майбутньому. Тому я написав proc_macro duang, щоб реалізувати його у формі макросу.

Наприклад:

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

Якщо ви використовуєте Іржа 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
Незважаючи на технічну точність, спільнота Руст голосно розділена щодо того, чи є це "хорошою" ідеєю чи ні. Я особисто потрапляю в "недобрий" табір.
Шепмастер

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.