obstruct
is an experimental implementation of anonymous structs and named arguments for Rust.
Create an anonymous struct with instruct!
and destructure it with destruct!
:
#![feature(associated_const_equality)]
use obstruct::{instruct, destruct};
// Create an anonymous struct.
let structured = instruct! { red: 0, green: 1.0, blue: 2 };
// Destructure that struct.
destruct! { let {red, green, blue} = structured };
assert_eq!(red, 0);
assert_eq!(green, 1.0);
assert_eq!(blue, 2);
Note that this is not (just) a tuple: the order in which fields are specified does not matter!
#![feature(associated_const_equality)]
use obstruct::{instruct, destruct};
// Create an anonymous struct.
let structured = instruct! { red: 0, green: 1.0, blue: 2 };
// Destructure that struct.
destruct! { let {blue, green, red} = structured };
assert_eq!(red, 0);
assert_eq!(green, 1.0);
assert_eq!(blue, 2);
If you attempt to access a field that doesn't exist, you will get a compile-time error:
#![feature(associated_const_equality)]
use obstruct::{instruct, destruct};
// Create an anonymous struct.
let structured = instruct! { red: 0, green: 1.0, blue: 2 };
// Destructure that struct.
destruct! { let {blue, green, oops} = structured };
// ^^^ --- will fail with a complex error message pointing at `oops`.
Create a function accepting named parameters with destruct!
and call it with call!
:
#![feature(associated_const_equality)]
use obstruct::{call, instruct, destruct};
// Create a function accepting anonymous arguments.
destruct!(fn do_something({red: u8, green: &'static str, blue: ()}) {
println!("Roses are {red}");
});
// Call this function
call!(do_something, {red: 0, green: "GREEN", blue: ()});
// Or equivalently
do_something(instruct! {red: 0, green: "GREEN", blue: ()});
Again, the order in which arguments are specified does not matter:
#![feature(associated_const_equality)]
use obstruct::{call, instruct, destruct};
// Create a function accepting anonymous arguments.
destruct!(fn do_something({red: u8, green: &'static str, blue: ()}) {
println!("Roses are {red}");
});
do_something(instruct! {blue: (), green: "GREEN", red: 0});
Again, errors are caught at compile-time:
#![feature(associated_const_equality)]
use obstruct::{call, instruct, destruct};
// Create a function accepting anonymous arguments.
destruct!(fn do_something({red: u8, green: &'static str, blue: ()}) {
println!("Roses are {red}");
});
do_something(instruct! {blue: (), green: "GREEN", oops: 0});
// ^^^ --- will fail with a complex error message pointing at `oops`.
call!(do_something, {red: 0, green: "GREEN", oops: ()});
// ^^^ --- will fail with a complex error message pointing at `oops`.
The core of obstruct
is a trait:
trait Field<T> {
const NAME: &'static str;
fn take(self) -> T;
}
Associated const NAME
is used to perform type assertions and catch typoes.
Every use of instruct!
or call!
is converted into an ordered tuple of fields,
with type-level information to ensure that we can perform type-checking on
field names.
#![feature(associated_const_equality)]
use obstruct::{call, instruct, destruct};
let rgb = instruct!{ red: 0, green: 1, blue: 2 };
// is essentially equivalent to
struct blue<T>(T);
struct green<T>(T);
struct red<T>(T);
impl<T> Field<T> for blue<T> {
const NAME: &'static str = "blue";
fn take(self) -> T {
self.0
}
}
impl<T> Field<T> green<T> {
const NAME: &'static str = "green";
fn take(self) -> T {
self.0
}
}
impl<T> Field<T> red<T> {
const NAME: &'static str = "red";
fn take(self) -> T {
self.0
}
}
let rgb = (blue(2), green(1), red(0));
Similarly, when you call destruct!
, fields are, once again ordered, so
#![feature(associated_const_equality)]
use obstruct::{call, instruct, destruct};
destruct!{let {red, green, blue} = rgb};
// is essentially equivalent to
let (blue, green, red) = rgb;
{
fn assert_type<T, U>(_: &T) where T: Field<T, NAME="blue"> {}
assert_type(&blue);
}
let blue = blue.0;
{
fn assert_type<T, U>(_: &T) where T: Field<T, NAME="green"> {}
assert_type(&green);
}
let green = green.0;
{
fn assert_type<T, U>(_: &T) where T: Field<T, NAME="red"> {}
assert_type(&green);
}
let red = red.0;
- Destructuring support for
ref
. - Destructuring support for
mut
. - Destructuring support for
_
. - Destructuring support for
aliases
. - Destructuring support for irrefutable patterns.
- I haven't checked how well
call!
anddestruct!
work with methods. - No
let else
yet. - No pattern-matching of any kind. No idea how to implement that.
- For the time being, everything produced by
instruct!
is a Voldemort type. This means that we cannot write
if foo {
instruct!{ red: 0 }
} else {
instruct!{ red: 1 }
}
- structx offers similar features. I haven't checked how it works yet.