Continue writing report

This commit is contained in:
Marko Korhonen 2020-05-02 21:11:26 +03:00
parent bc3fe77c93
commit f36c9fef60
No known key found for this signature in database
GPG key ID: 911B85FBC6003FE5
22 changed files with 266 additions and 165 deletions

View file

@ -1,82 +1,70 @@
\chapter{Kielet}
\section{Rust}
Rust\cite{rust:lang} on Mozillan 2010 julkaisema ohjelmointikieli. Se on hyvin suorituskykyinen järjestelmätason ohjelmointikieli, muistuttaen monilta osin C ja C++ kieliä. Rustin tarkoituksena on säilyttää näiden vanhojen kielien suorituskyky, mutta kuitenkin tarjota samalla muun muassa vahva tyypitys ja taattu turvallinen rinnakaisajo. Lisäksi tyypilliset C-kielien muistinhallintaongelmat on pyritty ratkaisemaan käytännöillä, jotka ovat samalla tehokkaita suorituskyvyn näkökulmasta, mutta myös helppoja käyttää ohjelmoijalle.
Rust \cite{rust:lang} on Mozillan 2010 julkaisema ohjelmointikieli. Se on hyvin suorituskykyinen järjestelmätason ohjelmointikieli, muistuttaen monilta osin C- ja C++ -kieliä. Rustin tarkoituksena on säilyttää näiden vanhojen kielien suorituskyky, mutta kuitenkin tarjota samalla muun muassa vahva tyypitys ja taattu turvallinen rinnakaisajo. Lisäksi tyypilliset C-kielien muistinhallintaongelmat on pyritty ratkaisemaan käytännöillä, jotka ovat samalla tehokkaita suorituskyvyn näkökulmasta, mutta myös helppoja käyttää ohjelmoijalle.
\subsection{Muistinhallinta}
Monissa korkean tason ohjelmointikielissä, esimerkiksi JavaScriptissä, on automaattinen roskienkeräys\cite{wiki:garbagecollection} (engl. garbage collector). Se on prosessi, joka siivoaa muistista käyttämättömiä tietoja ja näin ollen vapauttaa muistia. Automaattisen roskienkeräyksen ongelma on, että se itsessään käyttää järjestelmän resursseja, ja roskienkeruu on hidasta.
Monissa korkean tason ohjelmointikielissä, esimerkiksi JavaScriptissä, on automaattinen roskienkeräys \cite{wiki:garbagecollection} (engl. garbage collector). Se on prosessi, joka siivoaa muistista käyttämättömiä tietoja ja näin ollen vapauttaa muistia. Automaattisen roskienkeräyksen ongelma on, että se itsessään käyttää järjestelmän resursseja, ja roskienkeruu on hidasta.
Automaattiselle roskienkeruulle on aikaisemmin ollut vaihtoehtona vain manuaalinen muistinhallinta, missä ohjelmoija varaa ja vapauttaa muistia tarpeen mukaan. Tämä on taas verratuna automaattiseen roskien keruuseen melko työlästä ja virheherkkää.
Rustin yhtenä pääominaisuutena on mainostettu sen uudenlaista näkökulmaa muistinhallintaan: omistajuutta\cite{rust:ownership}. Siinä jokaisella arvolla on omistaja, ja kun omistaja menee näkyvyysalueen ulkopuolelle, niin menevät myös sen omistamat arvotkin, eli ne vapautetaan muistista. Arvojen omistajuutta voi siirtää joko pysyvästi tai väliaikaisesti lainaamalla. Otetaan esimerkkinä seuraava yksinkertainen koodi:
\clearpage
Rustin yhtenä pääominaisuutena on mainostettu sen uudenlaista näkökulmaa muistinhallintaan: omistajuutta \cite{rust:ownership}. Siinä jokaisella arvolla on omistaja, ja kun omistaja menee näkyvyysalueen ulkopuolelle, niin menevät myös sen omistamat arvotkin, eli ne vapautetaan muistista. Arvojen omistajuutta voi siirtää joko pysyvästi tai väliaikaisesti lainaamalla.
\begin{minted}{Rust}
fn say_hello(name: String) {
println!("Hello {}!", name);
}
\begin{code}
\inputminted{Rust}{code/ownership.rs}
\captionof{listing}{Omistajuus}
\label{code:rust:ownership}
\end{code}
fn main() {
let name = String::from("Marko");
say_hello(name);
Koodiesimerkin \ref{code:rust:ownership} rivillä 9 tapahtuva ''name'' -arvon tulostus ei toimi, koska arvon omistajuus on siirretty funktiolle ''say\char`_hello''. Funktion suorittamisen jälkeen se poistuu näkyvyysalueelta ja kaikki sen omistamat arvot vapautetaan muistista. Näin ollen arvoa ''name'' ei ole enää olemassa, kun sitä yritetään käyttää rivillä 9. Koodi saadaan toimimaan pienellä muutoksella:
println!("{}", name);
}
\end{minted}
\begin{code}
\inputminted{Rust}{code/borrow.rs}
\captionof{listing}{Lainaus}
\label{code:rust:borrow}
\end{code}
Rivillä 9 tapahtuva ''name'' -arvon tulostus ei toimi, koska arvon omistajuus on siirretty funktiolle ''say\char`_hello''. Funktion suorittamisen jälkeen se poistuu näkyvyysalueelta ja kaikki sen omistamat arvot vapautetaan muistista. Näin ollen arvoa ''name'' ei ole enää olemassa kun sitä yritetään käyttää rivillä 9. Koodi saadaan toimimaan pienellä muutoksella:
\begin{minted}{Rust}
fn say_hello(name: &String) {
println!("Hello {}!", name);
}
fn main() {
let name = String::from("Marko");
say_hello(&name);
println!("{}", name);
}
\end{minted}
Tässä esimerkissä omistajuuden siirtäminen on korvattu lainauksella. Lainauksessa arvon omistajuus säilyy nykyisellään ja lainaaja antaa itse arvon sijasta viitteen(eng. reference). Viite on osoitin, joka osoittaa samaan muistipaikkaan kuin missä alkuperäinen arvo on. Lainaaminen tehdään käyttämällä merkkiä ''\char`&''.
Koodiesimerkissä \ref{code:rust:borrow} omistajuuden siirtäminen on korvattu lainauksella. Tämä muutos pitää tehdä sekä funktion parametrien määritykseen riville 1, että funktion kutsuun riville 7. Lainauksessa arvon omistajuus säilyy nykyisellään ja lainaaja antaa itse arvon sijasta viitteen(eng. reference). Viite on osoitin, joka osoittaa samaan muistipaikkaan kuin missä alkuperäinen arvo on. Lainaaminen tehdään käyttämällä merkkiä ''\char`&''.
\clearpage
\subsection{Vahva tyypitys}
Rust on vahvasti tyypitetty kieli, mikä tarkoittaa sitä, että kaikkien arvojen tyypit pitää olla tiedossa ohjelman kokoamisen aikana. Tähän sisältyy myös funktioiden parametrit ja paluuarvot. Usein Rustin kääntäjä osaa päätellä arvojen tyypit itse, varsinkin yksinkertaisissa tapauksissa:
\begin{minted}{Rust}
let name = "Marko"; // string slice
let age = 26; // integer
\end{minted}
Rust on vahvasti tyypitetty kieli, mikä tarkoittaa sitä, että kaikkien arvojen tyypit pitää olla tiedossa ohjelman kokoamisen aikana. Tähän sisältyy myös funktioiden parametrit ja paluuarvot. Usein Rustin kääntäjä osaa päätellä (eng. inference) arvojen tyypit itse, varsinkin yksinkertaisissa tapauksissa:
\begin{code}
\inputminted{Rust}{code/type-inference.rs}
\captionof{listing}{Tyypin päättely}
\label{code:rust:inference}
\end{code}
Niissä tapauksissa joissa tyypille voi olla useita vaihtoehtoja, tai silloin jos arvon määrityksen yhteydessä tapahtuu konversio, ohjelmoijan tulee määrittää tyyppi. Tyypin voi määrittää näin:
\begin{minted}{Rust}
let name: &str = "Marko";
let age: u8 = 26;
\end{minted}
\begin{code}
\inputminted{Rust}{code/type-notation.rs}
\captionof{listing}{Tyypin merkintä}
\label{code:rust:type-notation}
\end{code}
Valitsin arvolle ''age'' tyypin \code{u8}, koska se on pienin kokonaislukutyypeistä ja sen arvo voi olla välillä 0-255. Näin voi potentiaalisesti vähentää ohjelman muistin käyttöä. Lisäksi tyypeillä voi myös karkeasti rajata funktion parametrejen arvojen vaihteluväliä. Esimerkiksi jos on kirjoittamassa funktiota joka ottaa parametrina henkilön iän, \code{u8} on hyvä valinta koska ihmisen ikä ei voi olla negatiivinen ja ihmiset myös harvoin elävät yli 255 vuotta.
Valitsin arvolle ''age'' tyypin u8, koska se on pienin kokonaislukutyypeistä ja sen arvo voi olla välillä 0-255. Näin voi potentiaalisesti vähentää ohjelman muistin käyttöä. Lisäksi tyypeillä voi myös karkeasti rajata funktion parametrejen arvojen vaihteluväliä. Esimerkiksi jos on kirjoittamassa funktiota joka ottaa parametrina henkilön iän, u8 on hyvä valinta koska ihmisen ikä ei voi olla negatiivinen ja ihmiset myös harvoin elävät yli 255 vuotta.
Edellä mainittu tyyppi \code{u8} on niin kutsuttu allekirjoittamaton kokonaisluku (eng. \textbf{u}nsigned). Allekirjoitetuissa kokonaisluvuissa (eng. signed) käytetään yksi bitti merkkaamaan sitä, onko luku positiivinen vai negatiivinen. Allekirjoittamattamattomissa luvuissa tätä ei tehdä, joten luku voi olla hieman isompi kuin allekirjoitettu luku, mutta se ei voi olla negatiivinen.
Edellä mainittu tyyppi u8 on niin kutsuttu allekirjoittamaton kokonaisluku (eng. \textbf{u}nsigned). Allekirjoitetuissa kokonaisluvuissa (eng. signed) käytetään yksi bitti merkkaamaan sitä, onko luku positiivinen vai negatiivinen. Allekirjoittamattamattomissa luvuissa tätä ei tehdä, joten luku voi olla hieman isompi kuin allekirjoitettu luku, mutta se ei voi olla negatiivinen.
\begin{table}[h!]
\label{tab:table1}
\begin{center}
\begin{minipage}{2in}
\begin{minipage}{2.5in}
\begin{tabular}{|c|c|c|}
\hline
\multicolumn{3}{|c|}{\textbf{Allekirjoittamaton}} \\
\hline
Tyyppi & Minimi & Maksimi \\
\hline
u8 & 0 & $2^{8}-1$\\
u16 & 0 & $2^{16}-1$\\
u32 & 0 & $2^{32}-1$\\
u64 & 0 & $2^{64}-1$\\
u128 & 0 & $2^{128}-1$\\
u8 & 0 & $ 2^{8}-1 $\\
u16 & 0 & $ 2^{16}-1 $\\
u32 & 0 & $ 2^{32}-1 $\\
u64 & 0 & $ 2^{64}-1 $\\
u128 & 0 & $ 2^{128}-1 $\\
\hline
\end{tabular}
\end{minipage}
@ -87,76 +75,48 @@ Edellä mainittu tyyppi \code{u8} on niin kutsuttu allekirjoittamaton kokonaislu
\hline
Tyyppi & Minimi & Maksimi \\
\hline
i8 & $-2^{7}$ & $2^{7}-1$\\
i16 & $-2^{15}$ & $2^{15}-1$\\
i32 & $-2^{31}$ & $2^{31}-1$\\
i64 & $-2^{64}$ & $2^{63}-1$\\
i128 & $-2^{127}$ & $2^{127}-1$\\
i8 & $ -2^{7} $ & $ 2^{7}-1 $\\
i16 & $ -2^{15} $ & $ 2^{15}-1 $\\
i32 & $ -2^{31} $ & $ 2^{31}-1 $\\
i64 & $ -2^{64} $ & $ 2^{63}-1 $\\
i128 & $ -2^{127} $ & $ 2^{127}-1 $\\
\hline
\end{tabular}
\end{minipage}
\end{center}
\caption{\textit{Rustin kokonaislukutyypit ja niiden vaihteluvälit}}
\caption{Rustin kokonaislukutyypit ja niiden vaihteluvälit}
\end{table}
Toisin kuin C- ja C++ -kielissä, Rustissa on oletuksena allekirjoittamattomien kokonaislukujen ylivuoto pois päältä. Tämä tarkoittaa sitä, että jos 8-bittisen allekirjoittamattoman kokonaisluvun arvo on esimerkiksi 256, siitä tulee 0. Rustin kääntäjä siis ei anna tällaisen tapahtua vaan kääntämisen yhteydessä tulee virheviesti:
\begin{minted}{shell}
error: literal out of range for `u8`
--> types.rs:2:19
|
2 | let age: u8 = 256;
| ^^^
|
= note: `#[deny(overflowing_literals)]` on by default
error: aborting due to previous error
\end{minted}
Toisin kuin C- ja C++ -kielissä, Rustissa on oletuksena allekirjoittamattomien kokonaislukujen ylivuoto pois päältä. Tämä tarkoittaa sitä, että jos 8-bittisen allekirjoittamattoman kokonaisluvun (u8) arvo on esimerkiksi 256, siitä tulee 0. Rustin kääntäjä siis ei anna tällaisen tapahtua vaan kääntämisen yhteydessä tulee virheviesti:
\begin{code}
\inputminted{shell}{code/integer-overflow}
\captionof{listing}{Allekirjoittamattoman kokonaisluvun ylivuoto}
\label{code:rust:integer-overflow}
\end{code}
\subsection{Muuttumaton data}
Rustissa kaikki arvot ovat oletuksena muuttumattomia (engl.\ immutable). Jos muuttumattoman datan sijasta tarvitsee muuttujia (engl. mutable), voi käyttää avainsanaa ''mut'', esimerkiksi \code{let mut name = "Marko"}. Myös lainaukset suoritetaan oletuksena muuttumattomasti ja muutettavan lainauksen voi tehdä samalla avainsanalla, esimerkiksi \code{say\char`_hello(\char`&mut name)}.
Rustissa kaikki arvot ovat oletuksena muuttumattomia (engl.\ immutable). Jos muuttumattoman datan sijasta tarvitsee muuttujia (engl. mutable), voi käyttää avainsanaa ''mut'', esimerkiksi \mintinline{Rust}{let mut name = "Marko"}. Myös lainaukset suoritetaan oletuksena muuttumattomasti ja muutettavan lainauksen voi tehdä samalla avainsanalla, esimerkiksi \mintinline{Rust}{say_hello(&mut name)}
Yleinen konsensus ohjelmoinnin maailmassa on se, että kaikki arvot mitä ei tarvitse muuttaa pitäisi nimenomaan määrittää muuttumattomana. Tämä on tuttua kaikille, jotka ovat tutustuneet funktionaalisiin ohjelmointikieliin. Muuttumaton data on myös todella tärkeää rinnakkaisajossa, missä useampi prosessi suorittaa samoja funktioita ja käsittelee samoja arvoja samaan aikaan.
Yleinen konsensus ohjelmoinnin maailmassa on se, että kaikki arvot, joita ei tarvitse muuttaa, pitäisi nimenomaan määrittää muuttumattomana. Tämä on tuttua kaikille, jotka ovat tutustuneet funktionaalisiin ohjelmointikieliin. Muuttumaton data on myös todella tärkeää rinnakkaisajossa, missä useampi prosessi suorittaa samoja funktioita ja käsittelee samoja arvoja samaan aikaan.
\subsection{Luotettavuus}
Rustia kehitettäessä on aina ollut tavoitteena luotettavuus. Tämä tarkoittaa sitä, että ohjelman virheet huomataan jo koontivaiheessa, eikä vasta suorituksen aikana ohjelman tietyssä tilassa. Tämän mahdollistavat edellä mainitut omistajuusmalli ja vahva tyypitys. Omistajuusmalli varmistaa sen, että ohjelmoija joutuu koodia kirjoittaessaan miettimään arvojen eliniän, joka tekee muistivuodoista harvinaisia. Vahva tyypitys varmistaa taas sen, että data on kaikkialla ohjelmassa yhteensopivaa.
Myös Rustin kääntäjään on panostettu paljon, ja virheiden sattuessa se on todella hyvä työkalu ohjelmoijalle. Se alleviivaa ongelmakohdat ja selittää lyhyesti mistä ongelma johtuu. Jossain tapauksissa kääntäjä jopa antaa pieniä koodin pätkiä mistä voi olla apua ongelman ratkaisemisessa.
Myös Rustin kääntäjään on panostettu paljon, ja virheiden sattuessa se on todella hyvä työkalu ohjelmoijalle. Se alleviivaa ongelmakohdat ja selittää lyhyesti, mistä ongelma johtuu. Jossain tapauksissa kääntäjä jopa antaa pieniä koodin pätkiä mistä voi olla apua ongelman ratkaisemisessa.
\subsection{Paketinhallinta}
Rustin paketinhallinta on toteutettu Cargo-nimisellä ohjelmalla. Sitä voi käyttää koko ohjelmiston elinkaaren ajan aina projektin luomisesta sen julkaisemiseen. Cargon käsittelemiä paketteja kutsutaan laatikoiksi (eng. crate), jotka julkaistaan crates.io\cite{rust:cratesio} pakettirekisterissä. Laatikot voivat myös olla riippuvaisia toisista laatikoista.
Rustin paketinhallinta on toteutettu Cargo-nimisellä ohjelmalla. Sitä voi käyttää koko ohjelmiston elinkaaren ajan aina projektin luomisesta sen julkaisemiseen. Cargon käsittelemiä paketteja kutsutaan laatikoiksi (eng. crate), jotka julkaistaan crates.io \cite{rust:cratesio} pakettirekisterissä. Laatikot voivat myös olla riippuvaisia toisista laatikoista.
Laatikon tiedot ja riippuvuudet määritetään \code{Cargo.toml} tiedostossa. Tässä esimerkki tämän opinnäytetyön yhteydessä tehdyn projektin palvelinpuolen \code{Cargo.toml}:
Laatikon tiedot ja riippuvuudet määritetään Cargo.toml tiedostossa.
\begin{minted}{TOML}
[package]
name = "thesis-backend"
version = "0.1.0"
authors = ["Marko Korhonen <marko.korhonen@reekynet.com>"]
edition = "2018"
\begin{code}
\inputminted{TOML}{code/Cargo.toml}
\captionof{listing}{Projektin palvelinpuolen Cargo.toml}
\label{code:rust:cargo-toml}
\end{code}
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "2.0.0"
actix-rt = "1.0.0"
serde = { version = "1.0.104", features = ["derive"] }
diesel = { version = "1.4.3", features = ["mysql", "r2d2", "chrono"] }
dotenv = "0.15.0"
bcrypt = "0.6.2"
env_logger = "0.7.1"
r2d2 = "0.8.8"
crypto = "0.0.2"
jsonwebtoken = "7.1.0"
chrono = { version = "0.4.11", features = ["serde"] }
actix-cors = "0.2.0"
actix-identity = "0.2.1"
futures = "0.3.4"
actix-files = "0.2.1"
\end{minted}
Cargoon on saatavilla myös useita liitännäisiä, esimerkiksi \code{cargo-watch}, joka suorittaa halutun toiminnon aina kun projektin sisällä tapahtuu muutoksia ja tässäkin projektissa käytetty \code{cargo-web}, joka helpottaa WebAssembly-ohjelmien kehittämistä.
Cargoon on saatavilla myös useita liitännäisiä, esimerkiksi cargo-watch, joka suorittaa halutun toiminnon aina kun projektin sisällä tapahtuu muutoksia ja tässäkin projektissa käytetty cargo-web, joka helpottaa WebAssembly-ohjelmien kehittämistä.
\subsection{Dokumentaatio ja yhteisö}
Rust on tunnettu todella laajasta dokumentaatiostaan ja vahvasta yhteisöstään. Molemmista on paljon apua varsinkin aloittejioille.
@ -164,6 +124,21 @@ Rust on tunnettu todella laajasta dokumentaatiostaan ja vahvasta yhteisöstään
Aloitin itsekin opiskelemaan Rustia vain hieman ennen tämän opinnäytetyön alkua ja vahvasta yhteisöstä oli monesti apua.
\section{WebAssembly}
WebAssembly on kehitteillä oleva asiakaspuolen ohjelmointikieli. Sitä on suunniteltu JavaScriptin seuraajaksi ja sen suurinpana etuna JavaScriptiin verrattuna on huomattavasti matalemman tason esitysmuoto, minkä ansiosta se on suorituskykyisempi.
WebAssembly \cite{webassembly:home} on kehitteillä oleva asiakaspuolen ohjelmointikieli. Sitä on suunniteltu JavaScriptin seuraajaksi ja sen suurimpana etuna JavaScriptiin verrattuna on huomattavasti matalamman tason esitysmuoto, minkä ansiosta se on suorituskykyisempi.
Kehittäjän ei ole tarkoitus kirjoittaa WebAssemblya suoraan, vaan käyttää työkaluja, joilla olemassa olevia ohjelmointikieliä voi koota WebAssemblyksi. Rust on tästä hyvä esimerkki, sillä WebAssembly on yksi sen kääntäjän natiiveista ''targeteista'', samalla tavalla kuin vaikka x86-prosessorit.
Kehittäjän ei ole tarkoitus kirjoittaa WebAssemblya itse, vaan käyttää työkaluja, joilla olemassa olevia ohjelmointikieliä voi kääntää WebAssemblyksi. Rust on tästä hyvä esimerkki, sillä WebAssembly on yksi sen kääntäjän natiiveista ''targeteista'', samalla tavalla kuin vaikka x86-prosessorit.
WebAssembly on siis ensisijaisesti binääriformaatti, mutta sen voi muuntaa myös tekstiformaatiksi, jonka nimi on WebAssembly text \cite{webassembly:text}. WebAssembly text käyttää S-lauseita, joten se muistuttaa syntaxiltaan hyvin paljon Lisp-ohjelmointikieltä.
\begin{code}
\inputminted{Lisp}{code/plus.wat}
\captionof{listing}{Lukujen yhteenlasku WebAssembly text -formaatissa}
\label{code:webassembly:plus}
\end{code}
\clearpage
\begin{code}
\inputminted{Rust}{code/plus.rs}
\captionof{listing}{Lukujen yhteenlasku Rustilla}
\label{code:rust:plus}
\end{code}