2017-01-13 3 views
3

Мне интересно иметь что-то функционально похожее на аргументы ключевого слова в Rust, где они в настоящее время не поддерживаются.Как наилучшим образом * поддельным * ключевыми словами аргументы функции стиля в Rust?

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

panel.button(label="Some Button") 
panel.button(label="Test", align=Center, icon=CIRCLE) 

Я видел это обрабатывается с помощью построителя-шаблон, например:

ui::Button::new().label("Some Button").build(panel) 
ui::Button::new().label("Test").align(Center).icon(CIRCLE).build(panel) 

Что хорошо, но в раз немного неудобно по сравнению с аргументами ключевого слова в Python.


Однако с помощью инициализации структуры с impl Default и Option<..> членов в Rust могут быть использованы, чтобы получить что-то очень близко к чему-то, который на практике похоже на написание ключевых аргументов, например:

ui::button(ButtonArgs { label: "Some Button".to_string(), .. Default::default() }); 

ui::button(ButtonArgs { 
    label: "Test".to_string(), 
    align: Some(Center), 
    icon: Some(Circle), 
    .. Default::default() 
}); 

Это работает, но имеет некоторые понижающие стороны в контексте попытки использовать в качестве ключевого слова args:

  • Необходимо префикс аргументов с именем struct
    (также необходимо явно включить его в пространство имен, добавив некоторые служебные данные).
  • Помещение Some(..) вокруг каждого необязательного аргумента является досадным/подробным.
  • .. Default::default() в конце каждого использования немного утомительно.

Существуют ли способы уменьшить некоторые из этих вопросов, (с помощью макросов, например), чтобы сделать эту работу более легко в качестве замены для доступа по ключевым словам?

+4

Я удалил свой ответ, но все еще кажется, что шаблон строитель может быть использован более элегантно, чем показано в этом вопросе. Если вы интегрируете в API, ваш вызывающий абонент может использовать что-то вроде: 'panel.button (« Test »). Align (Center) .icon (CIRCLE) .make()'. Это в точности эквивалентно Python ключевым словам args (предоставляет значения по умолчанию, позволяет произвольный порядок), и не выглядит неудобно вообще. – user4815162342

+0

Согласитесь, я использовал стиль строителя, и было предложено объявить, что структура может быть альтернативой ... пока я уверен, что некоторые макросы могут избежать некоторых накладных расходов, он выглядит нетривиальным и даже тогда будет неудобным. – ideasman42

+1

Boost.Process (C++) имеет интересное злоупотребление операцией перегрузки. Он создает константу 'label', а затем, когда вы используете' label = "Test", это по очереди создает объект, подобный тому, что будет делать Label {label: "Test"} '. Процесс Boost сочетает это с переменными аргументами, чтобы получить список таких объектов, которые затем дополняются значениями по умолчанию. Вы заканчиваете с 'panel.button (label =" Test ", align = Center, icon = CIRCLE);' вызывает ... однако неясно, как это будет транслироваться в Rust (нужно было бы выбрать другой оператор, чем '=' и, возможно, потребуется передать массив, так как без вариаций?). –

ответ

1

Вы можете воспользоваться From trait; Таким образом, вы можете оставить некоторые из шаблонного:

use self::Shape::*; 
use self::Alignment::*; 

#[derive(Debug)] 
struct Button { 
    label: String, 
    align: Option<Alignment>, 
    icon: Option<Shape>, 
} 

#[derive(Debug)] 
enum Shape { Circle } 

#[derive(Debug)] 
enum Alignment { Center } 

impl From<(&'static str, Alignment, Shape)> for Button { 
    fn from((l, a, i): (&'static str, Alignment, Shape)) -> Self { 
     Button { 
      label: l.to_owned(), 
      align: Some(a), 
      icon: Some(i) 
     } 
    } 
} 

fn main() { 
    let b: Button = ("button", Center, Circle).into(); 

    println!("{:?}", b); 
} 

Эта реализация будет работать специально для (&'static str, Alignment, Shape) кортежей; Однако, вы могли бы дополнительно реализовать From<&'static str>, который будет производить Button с данной label и None для других компонентов:

impl From<&'static str> for Button { 
    fn from(l: &'static str) -> Self { 
     Button { 
      label: l.to_owned(), 
      align: None, 
      icon: None 
     } 
    } 
} 

let b2: Button = "button2".into(); 
+0

'Button = (" button ", Center, Circle) .into();' больше похоже на регулярные аргументы функции, поскольку для каждого аргумента нет ключевых слов. Как это работает для дополнительных аргументов? поэтому он может принимать как 'let b: Button = (" button ", Center, Circle) .into();' и 'let b: Button = (" button "). into();'? – ideasman42

+0

Я расширил ответ, включив эту функциональность. – ljedrz

+0

Означает ли это, что вам нужно объявить функцию 'from' для каждой комбинации аргументов? Кроме того, если у вас есть два или более дополнительных аргумента одного типа, не знаете, как это можно сделать разумным способом. – ideasman42

3

Отказа от ответственности: Я советую против использования этого решения, поскольку ошибки сообщенных являются противными. Самое чистое решение, кодовое, скорее всего, это шаблон строителя.


С этим из пути ... Я взбитый вместе доказательства о концепции злоупотребления эксплуатанта.

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

С другой стороны, он страдает от необходимости импортировать множество символов (каждое имя, которое будет использоваться).

Это выглядит следующим образом:

// Rust doesn't allow overloading `=`, so I picked `<<`. 
fn main() { 
    let p = Panel; 
    p.button(LABEL << "Hello", ALIGNMENT << Alignment::Center); 

    p.button(LABEL << "Hello", Alignment::Left); 
    p.button(Label::new("Hello"), Alignment::Left); 
} 

Обратите внимание, что имя действительно необязательно, она просто серверы в качестве строителя для самого аргумента, но если у вас уже есть аргумент, который может быть избегали. Это также означает, что, вероятно, не стоит создавать имя для «очевидных» параметров (Alignment здесь).

Нормальное определение button:

#[derive(Debug)] 
struct Label(&'static str); 

#[derive(Debug)] 
enum Alignment { Left, Center, Right } 

struct Panel; 

impl Panel { 
    fn button(&self, label: Label, align: Alignment) { 
     println!("{:?} {:?}", label, align) 
    } 
} 

Требуется некоторое увеличение:

impl Carrier for Label { 
    type Item = &'static str; 
    fn new(item: &'static str) -> Self { Label(item) } 
} 

impl Carrier for Alignment { 
    type Item = Alignment; 
    fn new(item: Alignment) -> Self { item } 
} 

const LABEL: &'static Argument<Label> = &Argument { _marker: PhantomData }; 
const ALIGNMENT: &'static Argument<Alignment> = &Argument { _marker: PhantomData }; 

И да, это не означает, что вы можете увеличить функцию/метод, определенный в 3-сторонней библиотеки.

Это подтверждается:

trait Carrier { 
    type Item; 
    fn new(item: Self::Item) -> Self; 
} 

struct Argument<C: Carrier> { 
    _marker: PhantomData<*const C>, 
} 

impl<C: Carrier> Argument<C> { 
    fn create<I>(&self, item: I) -> C 
     where I: Into<<C as Carrier>::Item> 
    { 
     <C as Carrier>::new(item.into()) 
    } 
} 

impl<R, C> std::ops::Shl<R> for &'static Argument<C> 
    where R: Into<<C as Carrier>::Item>, 
      C: Carrier 
{ 
    type Output = C; 
    fn shl(self, rhs: R) -> C { 
     self.create(rhs) 
    } 
} 

Обратите внимание, что это НЕ адрес:

  • из порядка передачи аргумента
  • необязательных аргументов

Если пользователь достаточно терпелив перечислить все возможные комбинации дополнительных параметров, решение как @ljedrz возможно:

struct ButtonArgs { 
    label: Label, 
    align: Alignment, 
    icon: Icon, 
} 

impl From<Label> for ButtonArgs { 
    fn from(t: Label) -> ButtonArgs { 
     ButtonArgs { label: t, align: Alignment::Center, icon: Icon::Circle } 
    } 
} 

impl From<(Label, Alignment)> for ButtonArgs { 
    fn from(t: (Label, Alignment)) -> ButtonArgs { 
     ButtonArgs { label: t.0, align: t.1, icon: Icon::Circle } 
    } 
} 

impl From<(Label, Icon)> for ButtonArgs { 
    fn from(t: (Label, Icon)) -> ButtonArgs { 
     ButtonArgs { label: t.0, align: Alignment::Center, icon: t.1 } 
    } 
} 

impl From<(Label, Alignment, Icon)> for ButtonArgs { 
    fn from(t: (Label, Alignment, Icon)) -> ButtonArgs { 
     ButtonArgs { label: t.0, align: t.1, icon: t.2 } 
    } 
} 

impl From<(Label, Icon, Alignment)> for ButtonArgs { 
    fn from(t: (Label, Icon, Alignment)) -> ButtonArgs { 
     ButtonArgs { label: t.0, align: t.2, icon: t.1 } 
    } 
} 

затем позволят всем из следующих комбинаций:

fn main() { 
    let p = Panel; 
    p.button(LABEL << "Hello"); 
    p.button((LABEL << "Hello")); 
    p.button((LABEL << "Hello", ALIGNMENT << Alignment::Left)); 
    p.button((LABEL << "Hello", ICON << Icon::Circle)); 
    p.button((LABEL << "Hello", ALIGNMENT << Alignment::Left, ICON << Icon::Circle)); 
    p.button((LABEL << "Hello", ICON << Icon::Circle, ALIGNMENT << Alignment::Left)); 

    p.button(Label::new("Hello")); 
    p.button((LABEL << "Hello", Alignment::Left, Icon::Circle)); 
} 

Дополнительный набор скобок необходим, когда есть больше чем один аргумент.

Однако есть большой недостаток: пользовательский интерфейс ухудшается при использовании неправильного набора параметров.

Результат вызова p.button("Hello"); является:

error[E0277]: the trait bound `ButtonArgs: std::convert::From<&str>` is not satisfied --> <anon>:124:7 
    | 124 |  p.button("Hello"); 
    |  ^^^^^^ the trait `std::convert::From<&str>` is not implemented for `ButtonArgs` 
    | 
    = help: the following implementations were found: 
    = help: <ButtonArgs as std::convert::From<Label>> 
    = help: <ButtonArgs as std::convert::From<(Label, Alignment)>> 
    = help: <ButtonArgs as std::convert::From<(Label, Icon)>> 
    = help: <ButtonArgs as std::convert::From<(Label, Alignment, Icon)>> 
    = help: and 1 others 
    = note: required because of the requirements on the impl of `std::convert::Into<ButtonArgs>` for `&str` 
+0

Вы, сэр, являются аргументом Лекса Лютхора из Руста. :) – user4815162342

+0

@ user4815162342: Я не совсем уверен, должен ли я чувствовать себя честным или оскорбленным;) –

Смежные вопросы