Rust для разработчиков C#: перечисления

Эта статья предназначена для разработчиков, которые уже знают, как разрабатывать программное обеспечение с помощью C#, чтобы показать, как перечисления работают в Rust, и исследовать альтернативы для выполнения тех же действий, которые мы привыкли делать в C#, а также новые возможности, предлагаемые в Ржавчина.

перечисления

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

Простые перечисления

Основное отличие заключается в том, как мы вызываем перечисление для его использования.

// C#
enum Country
{
    Brazil,
    England,
    UnitedStates
}

// ...
Country country = Country.Brazil;
// Rust
enum Country {
    Brazil,
    England,
    UnitedStates
}

// ...
let country: Country = Country::Brazil;

Перечисления со связанными фиксированными значениями

Почти никакой разницы между C# и Rust.

// C#
enum HttpStatus
{
    OK = 200,
    NotFound = 404
}

// ...
HttpStatus status = HttpStatus.OK;
int statusCode = (int)HttpStatus.OK;
// Rust
enum HttpStatus {
    OK = 200,
    NotFound = 404,
}

// ...
let status: HttpStatus = HttpStatus::OK;
let statusCode: i32 = HttpStatus::OK as i32;

Перечисления с пользовательскими типами

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

// C#
enum ErrorCode : ushort
{
    None = 0,
    ConnectionLost = 100,
}

// ...
ushort errorCode = (ushort)ErrorCode.None;
// Rust
enum EnumA { // Each variant has 1 byte
    A = 1,
    B = 2
}

enum EnumB { // Each variant has 2 bytes
    A = 1000,
    B = 2
}

enum EnumC { // Each variant has 4 bytes
    A = 100000,
    B = 2
}

Перечисления как битовые флаги

В Rust нет прямого эквивалента, но есть много других альтернатив, и один из удобных способов — это импорт и использование крейта bitflags, который представляет собой макрос.

// C#
[Flags]
enum WeekDays
{
    None      = 0b_0000_0000,  // 0
    Monday    = 0b_0000_0001,  // 1
    Tuesday   = 0b_0000_0010,  // 2
    Wednesday = 0b_0000_0100,  // 4
    Thursday  = 0b_0000_1000,  // 8
    Friday    = 0b_0001_0000,  // 16
    Saturday  = 0b_0010_0000,  // 32
    Sunday    = 0b_0100_0000,  // 64
    Weekend   = Saturday | Sunday
}

// ...
WeekDays today = WeekDays.Wednesday;
bool isWeekend = today.HasFlag(WeekDays.Weekend);
bool isWednesday = today.HasFlag(WeekDays.Wednesday);
// Rust
use bitflags::bitflags;

bitflags! {
    struct WeekDays: u8 {
        const MONDAY = 0b00000001;
        const TUESDAY = 0b00000010;
        const WEDNESDAY = 0b00000100;
        const THURSDAY = 0b00001000;
        const FRIDAY = 0b00010000;
        const SATURDAY = 0b00100000;
        const SUNDAY = 0b01000000;
        const WEEKEND = Self::SATURDAY.bits | Self::SUNDAY.bits;
    }
}

// ...
let today: WeekDays = WeekDays::WEDNESDAY;
let is_weekend: bool = today.intersects(WeekDays::WEEKEND);
let is_wednesday: bool = today.contains(WeekDays::WEDNESDAY);

Перечисления со связанными неизвестными значениями, но известными типами

Ничего подобного в C# нет. В Rust хорошим подходом к работе со связанными значениями каждого варианта является использование ключевых слов match и impl.

// Rust
enum IpAddr {
    V4(String),
    V6(String),
}

impl IpAddr {
    fn to_string(&self) -> &String {
        match self {
            IpAddr::V4(ip) => ip,
            IpAddr::V6(ip) => ip,
        }
    }
    fn is_ipv4(&self) -> bool {
        match self {
            IpAddr::V4(_) => true,
            IpAddr::V6(_) => false,
        }
    }
}

// ...
let loopback: IpAddr = IpAddr::V4(String::from("127.0.0.1"));
print!("Loopback: {}", loopback.to_string());
println!("Is IPv4? {}", loopback.is_ipv4());
// Rust
enum CanvasCommand {
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl CanvasCommand {
    fn apply(&self) {
        match self {
            CanvasCommand::Move { x, y } => println!("Move to {}, {}", x, y),
            CanvasCommand::Write(text) => println!("Write {}", text),
            CanvasCommand::ChangeColor(r, g, b) => println!("Change color to {}, {}, {}", r, g, b),
        }
    }
}

// ...
let drawing: Vec<CanvasCommand> = vec![
    CanvasCommand::Move { x: 10, y: 10 },
    CanvasCommand::Write(String::from("Hello")),
    CanvasCommand::Move { x: 20, y: 20 },
    CanvasCommand::Write(String::from("World")),
    CanvasCommand::ChangeColor(0, 0, 0),
];

for command in drawing {
    command.apply();
}