Jste zde

Vlastní metoda na standardním datovém typu? V Rustu žádný problém

Podíváme se na to, jak si lze v jazyce Rust jednoduše implementovat metodu na libovolném typu. Klidně i na standardním, vestavěném typu, jako je celé číslo. Něco takového bychom v jazycích jako C++, Java či Python hledali marně.

Úvod

Dejme tomu, že bychom si chtěli rozšířit některý existující datový typ. Např. bychom místo volání vlastní funkce is_even() v následujícím kódu

let n = 20;
if is_even(n) {
    println!("{} is even", n);
}

chtěli napsat tento kód:

if n.is_even() {

V JavaScriptu to není problém. Ovšem v jiných jazycích, jako je C++, Java či Python bychom tuto možnost hledali marně. Co na to říká Rust?

Rozšíření existujících datových typů v Rustu

V Rustu to není problém. Můžeme si zvolit libovolný existující typ (klidně vestavěný) a rozšířit jej. Ukážeme si na to příkladu přidání metod is_even() a is_odd() pro i32, což je standardní 32b celočíselný typ. Jak již název těchto metod napovídá, budeme pomocí nich zjišťovat, zda je číslo sudé či liché.

Nejdříve si musíme vytvořit tzv. trait. Česky by se tento termín dal přeložit asi jako rys či vlastnost. Bude nám popisovat, jaké metody musí typ implementovat, aby měl danou vlastnost. Náš trait nazveme Parity:

trait Parity {
    fn is_even(&self) -> bool;
    fn is_odd(&self) -> bool;
}

Tímto říkáme, že typ, který má podporovat dotazování na sudost či lichost, musí implementovat tyto dvě metody. Zápis mluví poměrně za sebe. Co by vás mohlo akorát zarazit, je ono &self. Ve stručnosti nyní proto tento zápis popíši. Přítomností self říkáme, že se bude jednat o instanční metody, tedy nikoliv o metody třídy, známe jako statické metody. Je to obdoba implicitního this v C++ a Javě a explicitního self v Pythonu. Přítomností ampersandu (&) pak říkáme, že metodu lze zavolat jak na objektu, tak i na referenci na daný objekt. Tedy není potřeba přímo objekt samotný, ale stačí reference na něj. Pokud by vás zajímaly detaily, tak mrkněte na dokumentaci a popis traitů z hlediska návrhu Rustu.

Nyní nám zbývá jen daný rys implementovat pro námi zvolený typ (i32). Implementace je přímočará:

impl Parity for i32 {
    fn is_even(&self) -> bool {
        self % 2 == 0
    }
 
    fn is_odd(&self) -> bool {
        !self.is_even()
    }
}

Číslo je sudé, pokud je beze zbytku dělitelné dvěma. V opačném případě je liché. Ještě bych zmínil, že oproti např. C a C++, které jsou orientovány na příkazy, je Rust orientovaný na výrazy. Proto stejně jako např. v Ruby stačí místo return x napsat jen x. Tedy z funkce se automaticky vrátí hodnota posledního výrazu.

Nyní nám již nic nebrání napsat kód z úvodu:

let n = 20;
if n.is_even() {
    println!("{} is even", n);
}

Lze dokonce napsat i toto:

123.is_even() // vrátí false

No není to cool? :)

Poznámky na závěr

Na závěr jsem si nechal pár postřehů.

  • Samozřejmě platí, že abyste danou metodu mohli volat na číslech, tak musí tento trait a jeho implementace být v daném kódu dostupná. Není to tak, že byste vytvořili a implementovali trait a ten by se automaticky stal dostupný v celém programu. Pokud je tento trait a jeho implementace v modulu parity, tak je tento modul nejdříve potřeba importovat.
  • Kód by šlo zjednodušit s využitím faktu, že is_odd() bude vždy vracet negaci is_even(). Tělo metody is_odd() lze proto specifikovat přímo v traitu jako tzv. default method:
    trait Parity {
        fn is_even(&self) -> bool;
     
        fn is_odd(&self) -> bool {
            !self.is_even()
        }
    }

    Stačí pak pouze implementovat is_even():

    impl Parity for i32 {
        fn is_even(&self) -> bool {
            self % 2 == 0
        }
    }
  • Kompletní kód je k dispozici u mě na GitHubu.

Komentáře

Ahoj, jako obvykle pěkný článek. Jen dodám, že C++ podobný mechanismus sice neobsahuje, ale C# už ano (od C# 3.0 myslím). Nazývá se Extension Methods a podobně jako tady se jedná o statické metody rozšiřující libovolný typ. S příchozím C# 7.0 se mluvilo dokonce i o Extension Properties, ale zda bude implementováno už nevím. Tak třeba se něco podobného dostane i do C++ :).

Zajímavé. Díky za info!

Přidat komentář