diff --git a/config.json b/config.json index 0f02de0c2..7e30d9cc2 100644 --- a/config.json +++ b/config.json @@ -814,7 +814,7 @@ "uuid": "0520ad82-75d9-450c-9267-d9758b3b0513", "core": false, "unlocked_by": "luhn", - "difficulty": 7, + "difficulty": 10, "topics": [ "bitwise", "generics", diff --git a/exercises/xorcism/.meta/description.md b/exercises/xorcism/.meta/description.md index 2e9436059..591325194 100644 --- a/exercises/xorcism/.meta/description.md +++ b/exercises/xorcism/.meta/description.md @@ -49,7 +49,6 @@ These traits will be useful: - [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) - [`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) -- [`ExactSizeIterator`](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html) - [`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) - [`Sized`](https://doc.rust-lang.org/std/marker/trait.Sized.html) diff --git a/exercises/xorcism/.meta/hints.md b/exercises/xorcism/.meta/hints.md new file mode 100644 index 000000000..93c471c36 --- /dev/null +++ b/exercises/xorcism/.meta/hints.md @@ -0,0 +1,7 @@ +## Lifetime of `munge` return value + +Due to the usage of the `impl Trait` feature, lifetime management may be a bit +tricky when implementing the `munge` method. You may find it easier to write +your own `struct` with an `Iterator` implementation and return that concrete +type, at least to get started. Ultimately, it's a good idea to try and implement +the solution using `Iterator` combinators directly. diff --git a/exercises/xorcism/README.md b/exercises/xorcism/README.md index e6185b19c..5a661915e 100644 --- a/exercises/xorcism/README.md +++ b/exercises/xorcism/README.md @@ -51,7 +51,6 @@ These traits will be useful: - [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) - [`Borrow`](https://doc.rust-lang.org/std/borrow/trait.Borrow.html) -- [`ExactSizeIterator`](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html) - [`IntoIterator`](https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) - [`Sized`](https://doc.rust-lang.org/std/marker/trait.Sized.html) @@ -82,6 +81,15 @@ appropriate direction. They use these traits: - [`Read`](https://doc.rust-lang.org/std/io/trait.Read.html) - [`Write`](https://doc.rust-lang.org/std/io/trait.Write.html) +## Lifetime of `munge` return value + +Due to the usage of the `impl Trait` feature, lifetime management may be a bit +tricky when implementing the `munge` method. You may find it easier to write +your own `struct` with an `Iterator` implementation and return that concrete +type, at least to get started. Ultimately, it's a good idea to try and implement +the solution using `Iterator` combinators directly. + + ## Rust Installation Refer to the [exercism help page][help-page] for Rust installation and learning diff --git a/exercises/xorcism/example.rs b/exercises/xorcism/example.rs index 8e3644682..ee5d6e0a0 100644 --- a/exercises/xorcism/example.rs +++ b/exercises/xorcism/example.rs @@ -17,48 +17,18 @@ pub struct Xorcism<'a> { pos: usize, } -/// For composability, it is important that `munge` returns an iterator compatible with its input. -/// -/// However, `impl Trait` syntax can specify only a single non-auto trait. -/// Therefore, we define this output trait with generic implementations on all compatible types, -/// and return that instead. -pub trait MungeOutput: Iterator + ExactSizeIterator {} -impl MungeOutput for T where T: Iterator + ExactSizeIterator {} - -/// WARNING: This could be construed as abusing the type system -/// -/// We need to be able to assert that a particular iterator has -/// an exact size. We need this because `iter::Zip` implements -/// `ExactSizeIterator` only if both its inputs implement `ExactSizeIterator`, evne though -/// it will always terminate on the shorter of them. -/// -/// We wouldn't need this type if negative trait bounds were possible, but it looks like -/// even in a post-chalk world, those are not likely to be added to the language, as they -/// could make new trait implementations into a breaking change. -/// -/// Essentially, this type is used to assert that an infinite iterator has an exact size, -/// of "the biggest number". -struct AssertExactSizeIterator(I); - -impl Iterator for AssertExactSizeIterator -where - I: Iterator, -{ - type Item = ::Item; - - fn next(&mut self) -> Option { - self.0.next() - } - - fn size_hint(&self) -> (usize, Option) { - // oops, all 1s - const SIZE: usize = !0; - (SIZE, Some(SIZE)) +/// Ideally this would be a method, but we run into lifetime +/// issues with that. For more information, see: +/// https://github.com/rust-lang/rust/issues/80518 +fn next_key_byte(key: &[u8], pos: &mut usize) -> u8 { + let b = key[*pos]; + *pos += 1; + if *pos >= key.len() { + *pos = 0; } + b } -impl ExactSizeIterator for AssertExactSizeIterator where I: Iterator {} - impl<'a> Xorcism<'a> { /// Create a new Xorcism munger from a key pub fn new(key: &'a Key) -> Xorcism<'a> @@ -69,29 +39,13 @@ impl<'a> Xorcism<'a> { Xorcism { key, pos: 0 } } - /// Increase the stored pos by the specified amount, returning the old value. - fn incr_pos(&mut self, by: usize) -> usize { - let old_pos = self.pos; - self.pos += by; - old_pos - } - - /// Produce the key iterator, offset by `pos`. - fn key<'b>(&mut self, pos: usize) -> impl 'b + MungeOutput - where - 'a: 'b, - { - AssertExactSizeIterator(self.key.iter().copied().cycle().skip(pos)) - } - /// XOR each byte of the input buffer with a byte from the key. /// /// Note that this is stateful: repeated calls are likely to produce different results, /// even with identical inputs. pub fn munge_in_place(&mut self, data: &mut [u8]) { - let pos = self.incr_pos(data.len()); - for (d, k) in data.iter_mut().zip(self.key(pos)) { - *d ^= k; + for d in data.iter_mut() { + *d ^= next_key_byte(self.key, &mut self.pos); } } @@ -99,16 +53,16 @@ impl<'a> Xorcism<'a> { /// /// Note that this is stateful: repeated calls are likely to produce different results, /// even with identical inputs. - pub fn munge<'b, Data, B>(&mut self, data: Data) -> impl 'b + MungeOutput + pub fn munge<'b, Data, B>(&'b mut self, data: Data) -> impl 'b + Iterator where - 'a: 'b, Data: IntoIterator, - ::IntoIter: 'b + ExactSizeIterator, + ::IntoIter: 'b, B: Borrow, { - let data = data.into_iter(); - let pos = self.incr_pos(data.len()); - data.zip(self.key(pos)).map(|(d, k)| d.borrow() ^ k) + let key = self.key; + let pos = &mut self.pos; + data.into_iter() + .map(move |d| d.borrow() ^ next_key_byte(key, pos)) } /// Convert this into a [`Writer`] diff --git a/exercises/xorcism/src/lib.rs b/exercises/xorcism/src/lib.rs index c91f4c19a..59adc045a 100644 --- a/exercises/xorcism/src/lib.rs +++ b/exercises/xorcism/src/lib.rs @@ -6,14 +6,6 @@ pub struct Xorcism<'a> { _phantom: std::marker::PhantomData<&'a u8>, } -/// For composability, it is important that `munge` returns an iterator compatible with its input. -/// -/// However, `impl Trait` syntax can specify only a single non-auto trait. -/// Therefore, we define this output trait with generic implementations on all compatible types, -/// and return that instead. -pub trait MungeOutput: Iterator + ExactSizeIterator {} -impl MungeOutput for T where T: Iterator + ExactSizeIterator {} - impl<'a> Xorcism<'a> { /// Create a new Xorcism munger from a key /// @@ -37,7 +29,7 @@ impl<'a> Xorcism<'a> { /// /// Should accept anything which has a cheap conversion to a byte iterator. /// Shouldn't matter whether the byte iterator's values are owned or borrowed. - pub fn munge(&mut self, data: Data) -> impl MungeOutput { + pub fn munge(&mut self, data: Data) -> impl Iterator { unimplemented!(); // this empty iterator silences a compiler complaint that // () doesn't implement ExactSizeIterator diff --git a/exercises/xorcism/tests/xorcism.rs b/exercises/xorcism/tests/xorcism.rs index 1ce62e95b..45c48e73b 100644 --- a/exercises/xorcism/tests/xorcism.rs +++ b/exercises/xorcism/tests/xorcism.rs @@ -6,22 +6,55 @@ use std::io::{Read, Write}; use xorcism::Xorcism; #[test] -fn identity() { +fn munge_in_place_identity() { let mut xs = Xorcism::new(&[0]); - let data = "This is super-secret, cutting edge encryption, folks."; + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + let mut output = input.to_owned(); + xs.munge_in_place(&mut output); - assert_eq!( - xs.munge(data.as_bytes()).collect::>(), - data.as_bytes() - ); + assert_eq!(&input, &output); } #[test] #[ignore] -fn munge_output_has_len() { +fn munge_in_place_roundtrip() { + let mut xs1 = Xorcism::new(&[1, 2, 3, 4, 5]); + let mut xs2 = Xorcism::new(&[1, 2, 3, 4, 5]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + let mut cipher = input.to_owned(); + xs1.munge_in_place(&mut cipher); + assert_ne!(&input, &cipher); + let mut output = cipher; + xs2.munge_in_place(&mut output); + assert_eq!(&input, &output); +} + +#[test] +#[ignore] +fn munge_in_place_stateful() { + let mut xs = Xorcism::new(&[1, 2, 3, 4, 5]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + + let mut cipher1 = input.to_owned(); + let mut cipher2 = input.to_owned(); + xs.munge_in_place(&mut cipher1); + xs.munge_in_place(&mut cipher2); + + assert_ne!(&input, &cipher1); + assert_ne!(&input, &cipher2); + assert_ne!(&cipher1, &cipher2); +} + +#[test] +#[ignore] +fn munge_identity() { let mut xs = Xorcism::new(&[0]); - let data = "The output must have a definite length"; - assert_eq!(xs.munge(data.as_bytes()).len(), 38); + let data = "This is super-secret, cutting edge encryption, folks."; + + assert_eq!( + xs.munge(data.as_bytes()).collect::>(), + data.as_bytes() + ); } #[test]