Skip to content

Instantly share code, notes, and snippets.

@Centril
Created May 5, 2018 20:22
Show Gist options
  • Save Centril/879683253d2d6124143a116d6dc8d20b to your computer and use it in GitHub Desktop.
Save Centril/879683253d2d6124143a116d6dc8d20b to your computer and use it in GitHub Desktop.
Unnamed structs

Unnamed structs

Nominal type equivalence

Let ≡n be the equivalence class of nominally equivalent types. A ≡n B denotes that A and B are nominally the same type.

{ foo: T, bar: U }n { bar: U, foo: T } // Order does not matter.

{ foo: T, bar: U }n { foo: T, bar: X } // All fields must have the same types.

{ foo: T, bar: U }n { foo: T, quux: U } // The name of the fields must be the same.

Coercions

To subsets of other unnamed structs

You can coerce { foo: T, bar: U } to { foo: T, } or { bar: U, }. Such a coercion performs a partial move.

struct NC;

let source: { v: NC, x: String,        y: u8,  z: u16 }
          = { v: NC, x: "foo".into(),  y: 2,   z: 3   };

let target: { x: String, y: u8 } = source; // OK! Partial move

drop(source.v); // OK! since partial move and 'v' was not moved.
drop(source.z);

However, &{ foo: T, bar: U } does not coerce to &{ foo: T, } or &{ bar: U, }. This also applies to &mut, *mut and *const.

To named structs [pending / scratch this?]

You can coerce unnamed structs to named ones. An example:

struct Foo { x: u8, y: u8 }

let bar = { x: 1, y: 2 };

let foo: Foo = bar;

FIXME: This seems hazardous and bad for understanding of code.

From named structs

You can coerce a named struct into an unnamed one. An example:

fn take_xyz({x, y, z}: {x: u8, y: u8, z: u8}) { ... }

struct Foo { x: u8, y: u8, z: u8 }

take_xyz(Foo { x: 1, y: 2, z: 3})

The privacy of the named struct must be respected; you may only do this if the fields referenced in the unnamed struct and the named struct are visible in the given context.

mod alpha {
    struct Foo { pub x: u8, y: u8 }

    let foo = Foo { x: 1, y: 2 };
    let bar: { x: u8, y: u8 } = foo;  // OK!
}

mod beta {
    let wibble: { x: u8, } = foo; // OK! (foo is from above)
    let wobble: { y: u8, } = foo; // ERR! Foo.y is not visible to 'beta'.
}

No Subtyping

The type { foo: T, bar: U } is not a subtype of { foo: T, } nor { bar: U, }! It is not a subtype because it does not fit with Rust's memory model. This should not be a problem since you can always use coercions.

All subtyping in Rust also flows from lifetimes, so this would be terribly inconsistent.

FRU syntax

You can use FRU syntax on unnamed structs like so:

let alpha = { a: 1, b: 2 };
let beta  = { a: 3, ..alpha };
let gamma = { b: 4, ..alpha }

assert_eq!(beta,  { a: 3, b: 2 });
assert_eq!(gamma, { a: 1, b: 4 });

Expansion

You can expand an unnamed struct with more fields like so:

let alpha = { a: 1, b: 2 };
let beta  = { c: 3, b: 0, ..alpha };

assert_eq!(beta, { a: 1, b: 0, c: 3});

Merge

You can merge two unnamed structs like so:

let alpha = { a: 1, b: 2 };
let beta  = { c: 3, d: 4 };
let gamma = { ..alpha, ..beta };

assert_eq!(gamma, { a: 1, d: 2, c: 3, d: 4 });

Order matters

The semantics of merging two unnamed structs is well defined as updating left to right such that the value is taken from the right-most unnamed field when there is overlap. An example:

assert_eq!( { ..{ a: 1, b: 2 }, ..{ b: 3, c: 4 } }
          , { a: 1, b: 3, c: 4 } );

Named structs from unnamed

You can update a named struct or even create one with FRU provided privacy checks out.

struct Foo { x: u8, y: u8 }

let bar = { x: 1, y: 2 };

let foo1 = Foo { ..bar }; // Create Foo from 'bar'.

let foo2 = Foo { x: 3, ..bar } // Update it from 'bar'.

Syntactic questions

How do you write the named 1-tuple? Like so:

{ field: type, } // type level

{ field: value, } // value level literal

Note in particular the , at the end. This is required, because you'd otherwise get an ambiguity with type ascription.

The syntax struct { field: type, .. } is not used since it is inconsistent with normal tuples.

impls

To make the system useful, the following impls are provided lazily:

[forall field. typeof(field) : Trait)] => Trait where Trait is one of

  • Clone, Copy,
  • Default,
  • Debug,
  • Hash, PartialEq, Eq, PartialOrd, Ord.
  • auto traits

Other possible impls:

  • Add, ..

Note that these impls are not expressible in Rust itself since the language gives no way to quantify over fields and their types.

Your own impls

Simply put, you may only implement traits for unnamed structs iff the trait is defined in your module. This restriction exists to preserve coherence.

@repax
Copy link

repax commented Jun 10, 2018

Very nice!

Would it be safe to allow coercion from unnamed structs to named ones if there's a structural match?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment