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 negaciis_even
(). Tělo metodyis_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.
C# Extension Methods
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++ :).
Re: C# Extension Methods
Zajímavé. Díky za info!