Mijn tweede kopje roest

Vorige week dronk ik mijn eerste kopje Rust. Ik heb concepten geleerd die vreemd zijn in de talen die ik ken: eigendom, lenen, en levens. Deze week wil ik de tweede beker drinken en zien waar het me toe leidt.

Zelfverwijzende typen

Het is tijd om nog een oefening uit het eerste examen te implementeren:

Gegeven een verzameling van Super, retourneer de subcollectie van degenen die een sidekick hebben.

We moeten het model wijzigen om a . toe te voegen sidekick attribuut van type Super.

De implementatie lijkt eenvoudig genoeg.

pub struct Super<'a> {
    pub super_name: &'a str,
    pub real_name: &'a str,
    pub power: u16,
    pub sidekick: Option<Super<'a>>,
}

Helaas compileert de bovenstaande code niet:

error[E0072]: recursive type `Super` has infinite size
 --> src/model.rs:2:1
  |
2 | pub struct Super<'a> {
  | ^^^^^^^^^^^^^^^^^^^^ recursive type has infinite size
...
6 |     pub sidekick: Option<Super<'a>>,
  |                   ----------------- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `Super` representable
  |
6 |     pub sidekick: Box<Option<Super<'a>>>,
  |

Het lijkt erop dat een type in Rust niet naar zichzelf kan verwijzen. Laten we de suggestie van de compiler volgen. Een “eenvoudige” oplossing is om een ​​referentie te gebruiken in plaats van een type.

pub struct Super<'a> {
    pub super_name: &'a str,
    pub real_name: &'a str,
    pub power: u16,
    pub sidekick: &'a Option<Super<'a>>,
}

Terwijl het type compileert, doen de tests dat niet:

error[E0515]: cannot return value referencing temporary value
  --> src/tests/samples.rs:5:5
   |
5  | /     Super {
6  | |         super_name: "Batman",
7  | |         real_name: "Bruce Wayne",
8  | |         power: 50,
9  | |         sidekick: &Some(robin()),
   | |                    ------------- temporary value created here
10 | |     }
   | |_____^ returns a value referencing data owned by the current function

Terug naar af. De compiler hintte ook op het gebruik van Box.

Alle waarden in Rust worden standaard toegewezen aan de stapel. Waarden kunnen in een kader worden geplaatst (toegewezen op de heap) door a . te maken Box<T>. Een doos is een slimme verwijzing naar een heap toegewezen waarde van het type T. Wanneer een doos buiten bereik raakt, wordt zijn destructor genoemd, wordt het innerlijke object vernietigd en wordt het geheugen op de hoop bevrijd.

Roest door voorbeeld — Doos, stapel en hoop

Hier is de nieuwe code die gebruikt Box:

pub struct Super<'a> {
    pub super_name: &'a str,
    pub real_name: &'a str,
    pub power: u16,
    pub sidekick: Box<Option<Super<'a>>>,
}

Hiermee wordt alles gecompileerd, inclusief de testvoorbeelden!

pub(in crate::tests) fn batman<'a>() -> Super<'a> {
    Super {
        super_name: "Batman",
        real_name: "Bruce Wayne",
        power: 50,
        sidekick: Box::from(Some(robin())),     // 1
    }
}

  • Gebruik de om een ​​variabele te “boxen” Box.from() functie.

Ten slotte is de oplossing eenvoudig:

pub mod j {
    use crate::model::Super;

    pub fn find_supers_with_sidekicks<'a>(supers: &'a Vec<Super<'a>>) -> Vec<&Super<'a>> {
        supers
            .iter()                             // 1
            .filter(|&s| s.sidekick.is_some())  // 2
            .collect()                          // 3
    }
}

  1. Herhaal de items van de vector.
  2. Houd alleen degenen die een sidekick hebben.
  3. Verzamel de resterende items.

Het testen van de oplossing brengt geen nieuwe inzichten in de taal.

Eigenschappen, eigenschappen, eigenschappen overal!

De volgende oefening luidt als volgt:

Groepeer de sidekicks van Super in twee groepen:

  • Een bevat de sidekicks van helden, Super` met de Alignment.GOOD; de andere die van schurken, met Alignment.EVIL`.
  • De `Kaart` bevat twee sleutels, Alignment.GOOD en Alignment.EVIL.
  • De waarden zijn respectievelijk de set van helden’ sidekicks en de set van schurken’.
  • Nee null waarden worden geaccepteerd in de ingestelde waarden.
  • De afwezigheid van een sidekick voor a Super zou geen uitzondering moeten maken looptijd.

Alignment lijkt me een goede kandidaat voor een enum type. Gelukkig biedt Rust inderdaad opsommingen. We moeten het model dienovereenkomstig bijwerken:

Model voor uitlijningen bijwerken

Het vertaalt zich in het volgende:

pub struct Super<'a> {
    pub super_name: &'a str,
    pub real_name: &'a str,
    pub power: u16,
    pub sidekick: Box<Option<Super<'a>>>,
    pub alignment: Alignment,
}

pub enum Alignment {
    Good, Neutral, Evil
}

De logica zelf is een vouwen. Het goede nieuws is dat Iter verschaft een fold functie. Laten we beginnen met de volgende code:

pub fn group_sidekicks_by_alignment<'a>(supers: &'a Vec<Super<'a>>) -> HashMap<Alignment, Vec<&'a Super<'a>>>  {
    let mut map = HashMap::new();                             // 1
    map.insert(Good, Vec::new());                             // 2
    map.insert(Evil, Vec::new());                             // 2
    supers
        .iter()
        .filter(|&s| s.sidekick.is_some())                    // 3
        .fold(map, |mut map, s| {                             // 4
            let value = map.entry(s.alignment).or_default();  // 5
            value.push(&s.sidekick.unwrap());                 // 6
            map                                               // 7
        })
}

  1. Maak de hash-kaart van het resultaat.
  2. Stel de sleutels en standaardwaarden in.
  3. Uitfilteren Super zonder hulpje.
  4. Vouwen!
  5. Verkrijg de vector die overeenkomt met de uitlijning van de sidekick.
  6. Voeg de sidekick toe aan de vorige vector.
  7. Vergeet niet de kaart terug te sturen.

Zoals verwacht mislukt het:

error[E0277]: the trait bound `model::Alignment: Eq` is not satisfied
  --> src/solutions.rs:29:17
   |
29 |             map.insert(Good, Vec::new());
   |                 ^^^^^^ the trait `Eq` is not implemented for `model::Alignment`

De sleutels van een HashMap vergeleken hoeven te worden. Uit de documentatie:

Het is vereist dat de sleutels de implementeren Eq en Hash eigenschappen, hoewel dit vaak kan worden bereikt door gebruik te maken van #[derive(PartialEq, Eq, Hash)]. Als u deze zelf implementeert, is het van belang dat de volgende eigenschap geldt:

k1 == k2 -> hash(k1) == hash(k2)

Met andere woorden, als twee sleutels gelijk zijn, moeten hun hashes gelijk zijn.

–Struct std::collections::HashMap1.0.0

Hoewel dat logisch is, zou ik verwachten dat opsommingen de to Eq en Hash eigenschappen standaard. Dat is niet het geval. Laten we ze expliciet toevoegen.

#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Alignment {
    Good, Evil,
}

De volgende compileerfout is de volgende:

error[E0507]: cannot move out of `s.alignment` which is behind a shared reference
  --> src/solutions.rs:35:39
   |
35 |                 let value = map.entry(s.alignment).or_default();
   |                                       ^^^^^^^^^^^ move occurs because `s.alignment` has type `model::Alignment`, which does not implement the `Copy` trait

Het is eenvoudig genoeg om de . toe te voegen Copy eigenschap om Alignment. Eerlijk gezegd begrijp ik niet waarom Rust het een zet vindt. Je moet ook implementeren Clone.

Clone is een supereigenschap van Copy, dus alles wat is Copy moet ook implementeren Clone.

– Wat is het verschil tussen kopiëren en klonen?

#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum Alignment {
    Good, Evil,
}

Soorten verplaatsen

De volgende fout is:

error[E0507]: cannot move out of `*s.sidekick` which is behind a shared reference
  --> src/solutions.rs:36:29
   |
36 |                 value.push(&s.sidekick.unwrap());
   |                             ^^^^^^^^^^
   |                             |
   |                             move occurs because `*s.sidekick` has type `Option<Super<'_>>`, which does not implement the `Copy` trait
   |                             help: consider borrowing the `Option`'s content: `s.sidekick.as_ref()`

Deze fout is wat moeilijker direct op te lossen. Laten we eens kijken naar de betrokken typen:

Variabele Type
s &Super
sidekick Box<Option<Super>>
value Vec<&Super>

Gezien het bovenstaande moeten we:

  1. Pak de Super.
  2. Pak de sidekick attribuut verpakt in een Option, die zelf is verpakt in een Box. Herinnering: de vorige stap heeft alle eruit gefilterd Super wie heeft er geen sidekick.
  3. zet een referentie naar de sidekick in de value.

Hier is een (de?) oplossing:

value.push((*s.sidekick).as_ref().unwrap());

Laten we ontleden in stappen om beter te begrijpen wat er gebeurt:

Stap Uitdrukking Type
1 s.sidekick Box<Option<Super>>
2 *s.sidekick Option<Super>
3 (*s.sidekick).as_ref() Option<&Super>
4 (*s.sidekick).as_ref().unwrap() &Super

Nitpick: maak een kaart op een functionele manier

Op dit punt wordt alles gecompileerd (en uitgevoerd). Toch heb ik een hekel aan de manier waarop ik de HashMap. Het gaat om veranderlijkheid:

let mut map = HashMap::new();
map.insert(Good, Vec::new());
map.insert(Evil, Vec::new());

Ik ben voorstander van een meer functionele onveranderlijke manier. Hier is het:

let map = [Good, Evil]
    .iter()
    .cloned()
    .map(|alignment| (alignment, Vec::new()))
    .collect();

Er is waarschijnlijk een manier om beide te “zippen” Alignment kaart en de Super vector, maar ik moet toegeven dat ik zippen minder leesbaar vind.

Conclusie

Deze week dronk ik de tweede kop Rust, en ik overleefde. Deze stap voelde minder vreemd aan dan de week ervoor: een goed teken. Blijf op de hoogte voor andere berichten over mijn Rust-leerreis!

De volledige broncode voor dit bericht is te vinden op Github.

Om verder te gaan:

Oorspronkelijk gepubliceerd op Een Java Geek op 6 junidit, 2021.

CreditSource link

We will be happy to hear your thoughts

Leave a reply

Pan-Belgium
Logo
Compare items
  • Total (0)
Compare
0
Shopping cart