Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tietotyypit

Jokainen arvo Rustissa on tiettyä tietotyyppiä, joka kertoo Rustille, millaista dataa määritellään, jotta se tietää, miten datan kanssa työskennellään. Tarkastelemme kahta tietotyyppien osajoukkoa: skalaareja ja yhdistelmiä.

Muista, että Rust on staattisesti tyypitetty kieli, mikä tarkoittaa, että sen täytyy tietää kaikkien muuttujien tyypit käännösaikana. Kääntäjä pystyy yleensä päättelemään, mitä tyyppiä haluamme käyttää arvon ja sen käytön perusteella. Tilanteissa, joissa useita tyyppejä on mahdollisia, kuten kun muunnimme String-tyypin numerotyypiksi parse-funktiolla “Arvauksen vertaaminen salaisnumeroon” -osiossa Luvussa 2, meidän täytyy lisätä tyyppiannotaatio, kuten tässä:

#![allow(unused)]
fn main() {
let guess: u32 = "42".parse().expect("Not a number!");
}

Jos emme lisää edellä olevan koodin : u32 -tyyppiannotaatiota, Rust näyttää seuraavan virheen, mikä tarkoittaa, että kääntäjä tarvitsee meiltä lisätietoa tietääkseen, mitä tyyppiä haluamme käyttää:

{{#include ../listings/ch03-common-programming-concepts/output-only-01-no-type-annotations/output.txt}}

Näet erilaisia tyyppiannotaatioita muille tietotyypeille.

Skalaarityypit

Skalaari-tyyppi edustaa yksittäistä arvoa. Rustissa on neljä pääasiallista skalaarityyppiä: kokonaisluvut, liukuluvut, totuusarvot ja merkit. Saatat tunnistaa nämä muista ohjelmointikielistä. Katsotaan, miten ne toimivat Rustissa.

Kokonaislukutyypit

Kokonaisluku on luku ilman murto-osaa. Käytimme yhtä kokonaislukutyyppiä Luvussa 2, u32-tyyppiä. Tämä tyyppijulistus osoittaa, että siihen liitetyn arvon pitäisi olla etumerkitön kokonaisluku (etumerkityt kokonaislukutyypit alkavat i:llä u:n sijaan), joka vie 32 bittiä tilaa. Taulukko 3-1 näyttää Rustin sisäänrakennetut kokonaislukutyypit. Voimme käyttää mitä tahansa näistä muunnoksista kokonaislukuarvon tyypin julistamiseen.

Taulukko 3-1: Kokonaislukutyypit Rustissa

PituusEtumerkittyEtumerkitön
8-bittineni8u8
16-bittineni16u16
32-bittineni32u32
64-bittineni64u64
128-bittineni128u128
Arkkitehtuurista riippuvaisizeusize

Jokainen muunnos voi olla joko etumerkitty tai etumerkitön ja sillä on eksplisiittinen koko. Etumerkitty ja etumerkitön viittaavat siihen, voiko luku olla negatiivinen—toisin sanoen, tarvitseeko luvulla olla etumerkki (etumerkitty) vai onko se aina positiivinen ja voidaan siten esittää ilman etumerkkiä (etumerkitön). Se on kuin lukujen kirjoittaminen paperille: Kun etumerkillä on merkitystä, luku näytetään plus- tai miinusmerkillä; kun on turvallista olettaa luvun olevan positiivinen, sitä ei näytetä etumerkillä. Etumerkityt luvut tallennetaan kahden komplementin esityksellä.

Jokainen etumerkitty muunnos voi tallentaa lukuja väliltä −(2n − 1) – 2n − 1 − 1, missä n on kyseisen muunnoksen käyttämien bittien määrä. Eli i8 voi tallentaa lukuja −(27) – 27 − 1, mikä vastaa −128 – 127. Etumerkittömät muunnokset voivat tallentaa lukuja 0 – 2n − 1, joten u8 voi tallentaa lukuja 0 – 28 − 1, mikä vastaa 0 – 255.

Lisäksi isize- ja usize-tyypit riippuvat tietokoneen arkkitehtuurista, jolla ohjelmasi suoritetaan: 64 bittiä 64-bittisellä arkkitehtuurilla ja 32 bittiä 32-bittisellä arkkitehtuurilla.

Voit kirjoittaa kokonaislukuliteraaleja missä tahansa Taulukossa 3-2 näytetyssä muodossa. Huomaa, että lukuliteraaleilla, jotka voivat olla useita numeerisia tyyppejä, voi olla tyyppiliite, kuten 57u8, tyypin määrittämiseksi. Lukuliteraalit voivat myös käyttää _-merkkiä visuaalisen erottimen roolissa lukujen lukemisen helpottamiseksi, kuten 1_000, jolla on sama arvo kuin 1000:lla.

Taulukko 3-2: Kokonaislukuliteraalit Rustissa

LukuliteraalitEsimerkki
Desimaali98_222
Heksadesimaali0xff
Oktaaliluku0o77
Binääri0b1111_0000
Tavu (u8 vain)b'A'

Mistä siis tiedät, mitä kokonaislukutyyppiä käyttää? Jos et ole varma, Rustin oletusarvot ovat yleensä hyvä lähtökohta: Kokonaislukutyypit oletuksena i32. Pääasiallinen tilanne, jossa käyttäisit isize- tai usize-tyyppiä, on kun indeksoit jotakin kokoelmaa.

Kokonaisluvun ylivuoto

Sanotaan, että sinulla on u8-tyyppinen muuttuja, joka voi pitää arvoja 0 – 255. Jos yrität muuttaa muuttujan arvoksi jotain tämän alueen ulkopuolelta, kuten 256, tapahtuu kokonaisluvun ylivuoto, mikä voi johtaa kahteen käyttäytymiseen. Kun käännetään debug-tilassa, Rust sisällyttää kokonaisluvun ylivuodon tarkistukset, jotka saavat ohjelmasi panikoimaan ajonaikana, jos tämä tapahtuu. Rust käyttää termiä panikointi, kun ohjelma päättyy virheeseen; käsittelemme paniikkeja tarkemmin “Palautumattomat virheet panic!-makrolla” -osiossa Luvussa 9.

Kun käännetään release-tilassa --release-lipulla, Rust ei sisällytä kokonaisluvun ylivuodon tarkistuksia, jotka aiheuttavat paniikin. Sen sijaan, jos ylivuoto tapahtuu, Rust suorittaa kahden komplementin kiertämisen. Lyhyesti sanottuna tyypin maksimiarvoa suuremmat arvot “kiertyvät” tyypin vähimmäisarvoon. u8-tapauksessa arvo 256 muuttuu 0:ksi, arvo 257 muuttuu 1:ksi ja niin edelleen. Ohjelma ei panikoi, mutta muuttujalla on arvo, joka ei todennäköisesti ole se, mitä odotit. Kokonaisluvun ylivuodon kiertämiskäyttäytymiseen luottaminen katsotaan virheeksi.

Käsitelläksesi ylivuodon mahdollisuuden eksplisiittisesti voit käyttää standardikirjaston tarjoamia metodiperheitä primitiivisille numeerisille tyypeille:

  • Kierrä kaikissa käännöstiloissa wrapping_*-metodeilla, kuten wrapping_add.
  • Palauta None-arvo, jos ylivuotoa tapahtuu checked_*-metodeilla.
  • Palauta arvo ja totuusarvo, joka osoittaa, tapahtuiko ylivuotoa overflowing_*-metodeilla.
  • Rajoita arvon minimi- tai maksimiarvoihin saturating_*-metodeilla.

Liukulukutyypit

Rustissa on myös kaksi primitiivistä liukulukutyyppiä, jotka ovat lukuja desimaalipisteellä. Rustin liukulukutyypit ovat f32 ja f64, joiden koot ovat 32 ja 64 bittiä. Oletustyyppi on f64, koska nykyaikaisilla prosessoreilla se on suunnilleen yhtä nopea kuin f32, mutta tarjoaa enemmän tarkkuutta. Kaikki liukulukutyypit ovat etumerkittyjä.

Tässä on esimerkki liukuluvuista toiminnassa:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-06-floating-point/src/main.rs}}
}

Liukuluvut esitetään IEEE-754-standardin mukaisesti.

Numeeriset operaatiot

Rust tukee perusmatemaattisia operaatioita, joita odotat kaikille lukutyypeille: yhteenlasku, vähennyslasku, kertolasku, jakolasku ja jakojäännös. Kokonaislukujako katkaisee kohti nollaa lähimpään kokonaislukuun. Seuraava koodi näyttää, miten käyttäisit kutakin numeerista operaatiota let-lausekkeessa:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-07-numeric-operations/src/main.rs}}
}

Jokainen näiden lausekkeiden lauseke käyttää matemaattista operaattoria ja evaluoituu yhdeksi arvoksi, joka sidotaan sitten muuttujaan. Liite B sisältää listan kaikista Rustin tarjoamista operaattoreista.

Totuusarvotyyppi

Kuten useimmissa muissa ohjelmointikielissä, totuusarvotyypillä Rustissa on kaksi mahdollista arvoa: true ja false. Totuusarvot ovat yhden tavun kokoisia. Rustin totuusarvotyyppi määritellään bool-avainsanalla. Esimerkiksi:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-08-boolean/src/main.rs}}
}

Pääasiallinen tapa käyttää totuusarvoja on ehtolausekkeiden kautta, kuten if-lauseke. Käsittelemme, miten if-lausekkeet toimivat Rustissa “Ohjausrakenteet” -osiossa.

Merkkityyppi

Rustin char-tyyppi on kielen primitiivisin aakkosellinen tyyppi. Tässä on esimerkkejä char-arvojen julistamisesta:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-09-char/src/main.rs}}
}

Huomaa, että määrittelemme char-literaalit yksinkertaisilla lainausmerkeillä toisin kuin merkkijonoliteraalit, jotka käyttävät kaksinkertaisia lainausmerkkejä. Rustin char-tyyppi on 4 tavun kokoinen ja edustaa Unicode-skaalaariarvoa, mikä tarkoittaa, että se voi edustaa paljon enemmän kuin pelkkää ASCII:a. Aksenttimerkit; kiina-, japani- ja koreankieliset merkit; emojit ja nollaleveyden välilyönnit ovat kaikki kelvollisia char-arvoja Rustissa. Unicode-skaalaariarvot ovat välillä U+0000U+D7FF ja U+E000U+10FFFF. Unicode:ssa “merkki” ei kuitenkaan ole oikea käsite, joten intuitiosi siitä, mikä on “merkki”, ei välttämättä vastaa sitä, mikä on char Rustissa. Käsittelemme tämän aiheen tarkemmin “UTF-8-koodatun tekstin tallentaminen merkkijonoilla” -osiossa Luvussa 8.

Yhdistelmätyypit

Yhdistelmätyypit voivat ryhmitellä useita arvoja yhdeksi tyypiksi. Rustissa on kaksi primitiivistä yhdistelmätyyppiä: tuplat ja taulukot.

Tuplatyyppi

Tupla on yleinen tapa ryhmitellä useita erityyppisiä arvoja yhdeksi yhdistelmätyypiksi. Tuplalla on kiinteä pituus: kun se on julistettu, se ei voi kasvaa tai kutistua.

Luomme tuplan kirjoittamalla pilkuilla erotetun listan arvoista sulkeiden sisään. Jokaisella paikalla tuplassa on tyyppi, eivätkä tuplan eri arvojen tyypit tarvitse olla samoja. Olemme lisänneet valinnaiset tyyppiannotaatiot tähän esimerkkiin:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-10-tuples/src/main.rs}}
}

Muuttuja tup sitoo koko tuplan, koska tuplaa pidetään yhtenä yhdistelmäelementtinä. Saadaksemme yksittäiset arvot tuplasta voimme käyttää kuvioiden täsmäyttämistä tuplan purkamiseen, näin:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-11-destructuring-tuples/src/main.rs}}
}

Tämä ohjelma luo ensin tuplan ja sitoo sen muuttujaan tup. Sitten se käyttää kuviota let-avainsanan kanssa ottaakseen tup:n ja muuttaakseen sen kolmeksi erilliseksi muuttujaksi, x, y ja z. Tätä kutsutaan purkamiseksi, koska se jakaa yhden tuplan kolmeen osaan. Lopuksi ohjelma tulostaa y:n arvon, joka on 6.4.

Voimme myös käyttää tuplaelementtiä suoraan pisteellä (.) ja sen jälkeen sen arvon indeksillä, johon haluamme päästä. Esimerkiksi:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-12-tuple-indexing/src/main.rs}}
}

Tämä ohjelma luo tuplan x ja käyttää sitten kunkin tuplaelementin indeksiä. Kuten useimmissa ohjelmointikielissä, tuplan ensimmäinen indeksi on 0.

Tuplaa ilman arvoja kutsutaan erityisellä nimellä yksikkö. Tämä arvo ja sen vastaava tyyppi kirjoitetaan molemmat () ja edustavat tyhjää arvoa tai tyhjää paluutyyppiä. Lausekkeet palauttavat implisiittisesti yksikköarvon, jos ne eivät palauta mitään muuta arvoa.

Taulukkotyyppi

Toinen tapa kerätä useita arvoja on taulukko. Toisin kuin tuplassa, jokaisella taulukon elementillä täytyy olla sama tyyppi. Toisin kuin joissakin muissa kielissä, Rustin taulukoilla on kiinteä pituus.

Kirjoitamme taulukon arvot pilkuilla erotettuna listana hakasulkeiden sisään:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-13-arrays/src/main.rs}}
}

Taulukot ovat hyödyllisiä, kun haluat datasi allokoituvan pinolle, kuten muutkin tähän mennessä nähdyistä tyypeistä, eikä keolle (käsittelemme pinoa ja keon tarkemmin Luvussa 4), tai kun haluat varmistaa, että sinulla on aina kiinteä määrä elementtejä. Taulukko ei kuitenkaan ole yhtä joustava kuin vektorityyppi. Vektori on standardikirjaston tarjoama samankaltainen kokoelmatyyppi, jonka koko saa kasvaa tai kutistua, koska sen sisältö on keossa. Jos et ole varma, käytätkö taulukkoa vai vektoria, käytä todennäköisesti vektoria. Luku 8 käsittelee vektoreita tarkemmin.

Taulukot ovat kuitenkin hyödyllisempiä, kun tiedät, ettei elementtien määrän tarvitse muuttua. Esimerkiksi jos käyttäisit kuukausien nimiä ohjelmassa, käyttäisit todennäköisesti taulukkoa vektorin sijaan, koska tiedät sen sisältävän aina 12 elementtiä:

#![allow(unused)]
fn main() {
let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];
}

Kirjoitat taulukon tyypin hakasulkeilla, jokaisen elementin tyypillä, puolipisteellä ja sitten taulukon elementtien määrällä, näin:

#![allow(unused)]
fn main() {
let a: [i32; 5] = [1, 2, 3, 4, 5];
}

Tässä i32 on kunkin elementin tyyppi. Puolipisteen jälkeen luku 5 osoittaa, että taulukko sisältää viisi elementtiä.

Voit myös alustaa taulukon sisältämään saman arvon jokaiselle elementille määrittämällä alkuarvon, sitten puolipisteen ja taulukon pituuden hakasulkeissa, kuten tässä:

#![allow(unused)]
fn main() {
let a = [3; 5];
}

a-niminen taulukko sisältää 5 elementtiä, jotka kaikki asetetaan aluksi arvoon 3. Tämä on sama kuin kirjoittaisi let a = [3, 3, 3, 3, 3];, mutta tiiviimmällä tavalla.

Taulukkoelementtien käyttö

Taulukko on yksi muistilohko tunnetulla, kiinteällä koolla, joka voidaan allokoida pinolle. Voit käyttää taulukon elementtejä indeksoinnilla, näin:

Filename: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-14-array-indexing/src/main.rs}}
}

Tässä esimerkissä first-niminen muuttuja saa arvon 1, koska se on taulukon indeksin [0] arvo. second-niminen muuttuja saa arvon 2 taulukon indeksistä [1].

Virheellinen taulukkoelementin käyttö

Katsotaan, mitä tapahtuu, jos yrität käyttää taulukon elementtiä taulukon lopun jälkeen. Sanotaan, että suoritat tämän koodin, samankaltaisena kuin arvauspeli Luvussa 2, saadaksesi taulukon indeksin käyttäjältä:

Filename: src/main.rs

{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-15-invalid-array-access/src/main.rs}}

Tämä koodi kääntyy onnistuneesti. Jos suoritat tämän koodin cargo run -komennolla ja kirjoitat 0, 1, 2, 3 tai 4, ohjelma tulostaa vastaavan arvon taulukon kyseisessä indeksissä. Jos sen sijaan kirjoitat numeron taulukon lopun jälkeen, kuten 10, näet tulosteen kuten tämä:

thread 'main' panicked at src/main.rs:19:19:
index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Ohjelma johti ajonaikaiseen virheeseen virheellisen arvon käytössä indeksointioperatiossa. Ohjelma päättyi virheilmoitukseen eikä suorittanut viimeistä println!-lausetta. Kun yrität käyttää elementtiä indeksoinnilla, Rust tarkistaa, että määrittämäsi indeksi on pienempi kuin taulukon pituus. Jos indeksi on suurempi tai yhtä suuri kuin pituus, Rust panikoi. Tämä tarkistus täytyy tehdä ajonaikana, erityisesti tässä tapauksessa, koska kääntäjä ei voi mahdollisesti tietää, minkä arvon käyttäjä kirjoittaa, kun hän suorittaa koodin myöhemmin.

Tämä on esimerkki Rustin muistiturvallisuusperiaatteista käytännössä. Monissa matalan tason kielissä tällaista tarkistusta ei tehdä, ja kun annat virheellisen indeksin, virheelliseen muistiin voidaan päästä. Rust suojaa sinua tämänkaltaiselta virheeltä poistumalla välittömästi sen sijaan, että sallisi muistin käytön ja jatkuisi. Luku 9 käsittelee lisää Rustin virheenkäsittelystä ja siitä, miten voit kirjoittaa luettavaa, turvallista koodia, joka ei panikoi eikä salli virheellistä muistin käyttöä.