Skip to content

Confusion with Xorcism exercise #1040

Closed
@snoyberg

Description

@snoyberg

A coworker pointed me to the Xorcism exercise, and pointed out some difficulty with it. We both ultimately worked through it, and the exercise overall was a great stress test of the interaction of lifetimes and impl Trait. However, IMO, in its current incarnation it deserves to be bumped from Medium to Hard. And even so, I would recommend providing some additional guidance in the instructions, and perhaps some simplifications.

Some parts of the exercise, in particular the ExactSizeIterator, seem to be unnecessary. My solution ultimately did not leverage that trait, and it's unclear why it would be needed from the test suite.

use std::borrow::Borrow;

#[derive(Clone)]
pub struct Xorcism<'a> {
    key: &'a [u8],
    index: usize,
}

fn next_key(key: &[u8], index: &mut usize) -> u8 {
    let b = key[*index];
    *index += 1;
    if *index >= key.len() {
        *index = 0;
    }
    b
}

impl<'a> Xorcism<'a> {
    pub fn new<Key: AsRef<[u8]> + ?Sized>(key: &'a Key) -> Xorcism<'a> {
        Xorcism {
            key: key.as_ref(),
            index: 0,
        }
    }

    pub fn munge_in_place(&mut self, data: &mut [u8]) {
        for b in data {
            *b ^= next_key(self.key, &mut self.index);
        }
    }

    pub fn munge<'b, Data>(&'b mut self, data: Data) -> impl Iterator<Item=u8> + 'b
    where
        Data: IntoIterator,
        Data::Item: Borrow<u8>,
        Data::IntoIter: 'b,
    {
        let key = &self.key;
        let index = &mut self.index;
        data.into_iter().map(move |b| *b.borrow() ^ next_key(key, index))
    }
}

I would also recommend introducing a few simple test cases of munge_in_place before munge, so that someone implementing it can get a feel for how munging is supposed to work before diving into the more complicated issues with streaming and lifetimes.

Finally, I would recommend making the impl Trait approach a bonus point, and instead recommend starting with a concrete struct. I looked at a number of the existing solutions on Exercism, and it seems like that is the most intuitive place to start. It also avoids the major complexity around lifetimes and impl Trait.

I'd be happy to open a PR to make some of these modifications, but wanted to start with a discussion issue first.

Note that I've also opened rust-lang/rust#80518 for some surprising compiler behavior around this exercise.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions