Skip to the content.

Universal and existential quantification in Rust, Swift and Haskell

In Rust, you can return Box<dyn X> (or other smart pointers) or impl X to return an existential type. You can hide an actual type using impl X without boxing a value.

trait X {}

struct X1;
impl X for X1 {}

// A callee decides a type which a caller can't know, boxed
fn x1() -> Box<dyn X> { Box::new(X1) }

// A callee decides a type which a caller can't know, unboxed
fn x2() -> impl X { X1 }

trait Y {
    fn new() -> Self;
}

struct Y1;
impl Y for Y1 {
    fn new() -> Self { Y1 }
}

// A caller decides a type
fn y1<T: Y>() -> T { T::new() }

fn main() {
    let v1 = x1();
    let v2 = x2();
    let v3: Y1 = y1();
    let v4 = y1::<Y1>();
}

Swift has very similar mechanics. Use any X instead of Box<dyn X>, and some X instead of impl X. Swift automatically boxes a value with any X when necessary.

protocol X {}

struct X1: X {}

// A callee decides a type which a caller can't know, boxed
func x1() -> any X { X1() }

// A callee decides a type which a caller can't know, unboxed
func x2() -> some X { X1() }

protocol Y {
    static func new() -> Self
}

struct Y1: Y {
    static func new() -> Y1 { Y1() }
}

// A caller decides a type
func y1<T: Y>() -> T { T.new() }

let v1 = x1()
let v2 = x2()
let v3: Y1 = y1()
// let v4 = y1<Y1>() // Swift doesn't allow this

Haskell doesn’t have an equivalent of impl X or some X since values are usually boxed.

import Data.Kind

type Some :: (Type -> Constraint) -> Type
data Some c where
  Some :: c a => a -> Some c

class X a

data X1 = X1
instance X X1

-- A callee decides a type which a caller can't know, boxed
x1 :: Some X
x1 = Some X1

class Y a where
  new :: a

data Y1 = Y1
instance Y Y1 where
  new = Y1

-- A caller decides a type
y1 :: Y a => a
y1 = new

v1 :: Some X
v1 = x1

v2 :: Y1
v2 = y1