The `Squeeze` challenge

Here's an odd little challenge: can you implement the squeeze function so that it passes the tests below?

I'm pretty sure this can't be implemented in stable Rust. I've implemented it using #[feature(optin_builtin_traits)]. It feels like it ought to be implementable using specialization, but I can't figure out how.

#[cfg(test)]
mod test {
    use super::squeeze;

    #[test]
    fn test_squeeze() {
        // The idea here is that any non-() type would work.
        assert_eq!(squeeze(1i32, 2i32), (1, 2));
        assert_eq!(squeeze(true, false), (true, false));
        assert_eq!(squeeze("foo", Some("bar")), ("foo", Some("bar")));

        assert_eq!(squeeze(1i32, ()), 1);
        assert_eq!(squeeze("foo", ()), "foo");
        assert_eq!(squeeze(Some(1.3), ()), Some(1.3));

        assert_eq!(squeeze((), 2i32), 2);
        assert_eq!(squeeze((), "foo"), "foo");
        assert_eq!(squeeze((), Some(1.3)), Some(1.3));

        assert_eq!(squeeze((), ()), ());
    }
}

Here's my solution. [edit] Unfortunately, it's not correct; see Cerberuser's comment.

Phew, that was a nightmare! I solved it with an Fn* impl on a mutable static variable. This code is a mess, yet I still find it to be beautiful given its complexity and what it achieves.
Here's my solution.
There were some necessary changes to be able to make it work:

#[cfg(test)]
mod test {
    use super::squeeze;

    #[test]
    fn test_squeeze() {
        unsafe {
            // The idea here is that any non-() type would work.
            assert_eq!(squeeze(1i32,      2i32),        (1, 2).into());
            assert_eq!(squeeze(true,      false),       (true, false).into());
            assert_eq!(squeeze("foo",     Some("bar")), ("foo", Some("bar")).into());

            assert_eq!(squeeze(1i32,      ()),          1.into());
            assert_eq!(squeeze("foo",     ()),          "foo".into());
            assert_eq!(squeeze(Some(1.3), ()),          Some(1.3).into());

            assert_eq!(squeeze((),        2i32),        2.into());
            assert_eq!(squeeze((),        "foo"),       "foo".into());
            assert_eq!(squeeze((),        Some(1.3)),   Some(1.3).into());

            assert_eq!(squeeze((),        ()),          ().into());
        }
    }
}

The changes were, that because we're operating on a static mutable object we need to use an unsafe block, and we also need to call .into() to cast the right hand expression of the assert_eq! to a Return<L, R>. There is some of unstable code in here though, so be careful with it.


Edit: Here's a better documented version of the code.

3 Likes

I did not expect a solution involving unsafe code and custom Fn trait impls! :astonished: Amazing!

Do you think you can get it going with the original test case?

1 Like

This seems a problem to solve with macros.

macro_rules! squeeze{
    ((),()) => {
        ()
    };
    ($a:expr,()) => {
        $a
    };
    ((),$b:expr) => {
        $b
    };
    ($a:expr,$b:expr) => {
        ($a,$b)
    };
}

I am surprised that there are ways to make it work with functions. I do not think rust is supposed to operate with types directly.

2 Likes

Easy:

#[derive(Debug)]
struct Good;

fn squeeze<T, U>(_: T, _: U) -> Good {
    Good
}

impl<T> PartialEq<T> for Good {
    fn eq(&self, other: &T) -> bool {
        true
    }
}

In the future, try to come up with some negative asserts, too.

25 Likes

Voilà
And here's a version that is stripped to the bare minimum.

2 Likes

Well, I've found the test case which seems to be sound (comparing to challenge itself), but fails to compile with your approach:

struct Flags(());
squeeze(Flags(()), 1i32);

This will not compile, since Flags is not NonUnit.

1 Like
  • Troll solution : it does make the test succeed, with no unstable features whatsoever, but just because it is exploiting the finite sample size of the test batteries. It is still an interesting solution for those needing such functionality for a finite set of types.

  • Remark regarding @jimb 's challenge and solution:
    for all T != (), T, () should give (T, ) instead of T (and same for the mirror).
    To see that, it should be noted that with the current constraints, one can write:

    impl<Y> Squeeze<Y> for () {
        type Output = Y;
        fn squeeze(self, other: Y) -> Y {
            other
        }
    }
    

    instead of

    impl<Y: NonTrivial> Squeeze<Y> for () {
        type Output = Y;
        fn squeeze(self, other: Y) -> Y {
            other
        }
    }
    
    impl Squeeze<()> for () {
        type Output = ();
        fn squeeze(self, _other: ()) -> () {
            ()
        }
    }
    

It doesn't work with @jimb's solution either.
Playground

I was looking more into this challenge and I broke the compiler.

thread 'rustc' panicked at 'internal error: entered unreachable code', src/libsyntax/parse/parser.rs:648:22
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

error: internal compiler error: unexpected panic

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

In the playground: Rust Playground

1 Like

The parse error (and ICE) can be reduced to just this part:

trait Howness {}
impl Howness for (){
    fn how_are_you(&self->Empty
    {
        Empty
    }
}

Stable has no ICE, but beta and nightly do. Please file a bug!

2 Likes

Done: Missing right parenthesis panics the compiler · Issue #58856 · rust-lang/rust · GitHub

2 Likes

I think I'm in love

Yeah, this is a serious problem. The auto traits don't work the way I assumed they did. Even types like Option<()> and ((), 10) will break it.

Aha! Another ICE:
Playground
Going to report it.

2 Likes

Which can be further reduced to

type A <T : !NotATrait>

note that it does not even end the parsing.

Well, I feel like if my puzzle-turned-question has caused at least one ICE it was a success...

11 Likes