How to avoid redundant code when implementing Index and IndexMut in Rust - rust

At the moment, implementing the std::ops::IndexMut trait on a type in Rust requires that I also implement the std::ops::Index trait as well. The bodies of these implementations end up being virtually identical. For example:
use std::ops::{Index, IndexMut};
enum IndexType {
A,
B,
}
struct Indexable {
a: u8,
b: u8,
}
impl Index<IndexType> for Indexable {
type Output = u8;
fn index<'a>(&'a self, idx: IndexType) -> &'a u8 {
match idx {
IndexType::A => &self.a,
IndexType::B => &self.b,
}
}
}
impl IndexMut<IndexType> for Indexable {
fn index_mut<'a>(&'a mut self, idx: IndexType) -> &'a mut u8 {
match idx {
IndexType::A => &mut self.a,
IndexType::B => &mut self.b,
}
}
}
fn main() {}
This works, and obviously for trivial types this isn't a serious problem, but for more complex types with more interesting indexing this quickly becomes laborious and error-prone. I'm scratching my head trying to find a way to unify this code, but nothing is jumping out at me, and yet I feel there has to/should be a way to do this without essentially having to copy and paste. Any suggestions? What am I missing?

Unfortunately, this cuts across a few things Rust really isn't good at right now. The cleanest solution I could come up with was this:
macro_rules! as_expr {
($e:expr) => { $e };
}
macro_rules! borrow_imm { ($e:expr) => { &$e } }
macro_rules! borrow_mut { ($e:expr) => { &mut $e } }
macro_rules! impl_index {
(
<$idx_ty:ty> for $ty:ty,
($idx:ident) -> $out_ty:ty,
$($body:tt)*
) => {
impl ::std::ops::Index<$idx_ty> for $ty {
type Output = $out_ty;
fn index(&self, $idx: $idx_ty) -> &$out_ty {
macro_rules! index_expr { $($body)* }
index_expr!(self, borrow_imm)
}
}
impl ::std::ops::IndexMut<$idx_ty> for $ty {
fn index_mut(&mut self, $idx: $idx_ty) -> &mut $out_ty {
macro_rules! index_expr { $($body)* }
index_expr!(self, borrow_mut)
}
}
};
}
enum IndexType { A, B }
struct Indexable { a: u8, b: u8 }
impl_index! {
<IndexType> for Indexable,
(idx) -> u8,
($this:expr, $borrow:ident) => {
match idx {
IndexType::A => $borrow!($this.a),
IndexType::B => $borrow!($this.b),
}
}
}
fn main() {
let mut x = Indexable { a: 1, b: 2 };
x[IndexType::A] = 3;
println!("x {{ a: {}, b: {} }}", x[IndexType::A], x[IndexType::B]);
}
The short version is: we turn the body of index/index_mut into a macro so that we can substitute the name of a different macro that, given an expression, expands to either &expr or &mut expr. We also have to re-capture the self parameter (using a different name) because self is really weird in Rust, and I gave up trying to make it work nicely.

Related

Possible to implement dynamically-typed linked list in safe Rust?

Is it possible to implement a dynamically-typed linked list in safe Rust?
The meaning of dynamically-typed linked list:
Node(56.7) -> Node("hello") -> Node(77) -> ...
I am doing this as an exercise, have tried different approaches to no avail, using Traits. Therefore doubting the possibility of doing this in Safe Rust.
If your list has to work with any type
You can use the Any trait or better yet another object-safe trait which has the functionality you need from items in the list.
enum Node {
Nil,
Node {
value: Box<dyn std::any::Any>,
next: Box<Node>,
},
}
impl std::fmt::Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Node::Nil => write!(f, "Nil"),
Node::Node { value, next } => {
if let Some(value) = value.downcast_ref::<f64>() {
write!(f, "Node({value}) -> {next:?}")
} else if let Some(value) = value.downcast_ref::<i32>() {
write!(f, "Node({value}) -> {next:?}")
} else if let Some(value) = value.downcast_ref::<&str>() {
write!(f, "Node({value:?}) -> {next:?}")
} else {
write!(f, "Node(<unknown type>) -> {next:?}")
}
}
}
}
}
fn main() {
let list = Node::Node {
value: Box::new(56.7),
next: Box::new(Node::Node {
value: Box::new("hello"),
next: Box::new(Node::Node {
value: Box::new(77),
next: Box::new(Node::Nil),
}),
}),
};
dbg!(list);
}
will output something like [src/main.rs:39] list = Node(56.7) -> Node("hello") -> Node(77) -> Nil to stderr.
Pros
Works with any object (implementing the trait).
Every Node has the size of two Boxes no matter the types you put into the list.
Cons
Using downcast* can become very awkward fast, and there isn't really much you can do with a dyn Any otherwise.
Necessary additional indirection of Box.
If you know the types that end up in the list
You can use an enum with every possible type:
#[derive(Debug)]
enum DynamicType {
Float(f64),
Int(i32),
Str(&'static str),
}
enum Node {
Nil,
Node {
value: DynamicType,
next: Box<Node>,
},
}
impl std::fmt::Debug for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Node::Nil => write!(f, "Nil"),
Node::Node { value, next } => write!(f, "Node({value:?}) -> {next:?}"),
}
}
}
fn main() {
use DynamicType::*;
let list = Node::Node {
value: Float(56.7),
next: Box::new(Node::Node {
value: Str("hello"),
next: Box::new(Node::Node {
value: Int(77),
next: Box::new(Node::Nil),
}),
}),
};
dbg!(list);
}
Pros
You can match and know instantly what type you're dealing with.
No indirection, the data is stored directly in the Nodes.
Cons
If the DynamicType contains a variety of sizes you might end up wasting a lot of space on Nodes.
You have to know every type to put in up front.

Calculate number of fields that are of type "None" in a struct Rust

I am working on a macro that computes the number of fields on a struct which are of type None at runtime. I have an implementation that computes the number of fields for a struct. The source code is given below.
use std::rc::Rc;
use std::cell::Cell;
macro_rules! generate {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
generate!(#count $($field),+)
}
}
};
(#count $t1:tt, $($t:tt),+) => { 1 + generate!(#count $($t),+) };
(#count $t:tt) => { 1 };
}
generate! { test1 { num: i32, s: Option<String> }}
generate! { test2 { num: i32, s: String }}
fn main() {
let s = test1 { num: 0, s: None };
println!("{}", s.field_count());
let s = test2 { num: 0, s: String::new() };
println!("{}", s.field_count());
}
The problem with this implementation is this returns the total number of fields in the
struct at compile time so for test1 and test1 the answer is always 2. I am looking for a way to modify the macro to compute fields that are of type "None" in the struct.
To distinguish Nones, we first need to distinguish Options. And this is little hard.
There are two ways to do that: syntactically or via the type system. Syntactically means that the macro will identify fields that have the type Option<T> (i.e. Option < type >). However, this requires a TT munching, will force you to parse the ty fragment type yourself (you cannot rely on it because you'll not be able to inspect into its composed tokens later), and as a bonus won't work if users will type-alias Option.
The type system approach is more robust. The idea is to create some trait, implemented for any type, with a method that for Option<T> will return 1 iff it is Some and for other types will always return 1. This sounds like a perfect use case for specialization (playground):
#![feature(min_specialization)]
pub trait OneIfSome {
fn one_if_some(&self) -> usize;
}
impl<T> OneIfSome for T {
default fn one_if_some(&self) -> usize { 1 }
}
impl<T> OneIfSome for Option<T> {
fn one_if_some(&self) -> usize { self.is_some() as usize }
}
macro_rules! generate {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
generate!(#count self $($field,)*)
}
}
};
(#count $self:ident $first_field:ident, $($rest:ident,)*) => {
$crate::OneIfSome::one_if_some(&$self.$first_field)
+ generate!(#count $self $($rest,)*)
};
(#count $self:ident) => { 0 };
}
Unfortunately, specialization is unstable, and doesn't even have a clear path towards stabilization. Can we replace it with something stable?
And it turns out, yes! #dtolnay invented a technique called "autoref specialization" that is intended exactly for cases like that (specialization inside a macro). I won't explain how it works here (the linked article is a great read), but here is how you can apply it to your case (playground):
pub trait OneIfSomeGeneral {
fn one_if_some(&self) -> usize;
}
impl<T> OneIfSomeGeneral for &'_ T {
fn one_if_some(&self) -> usize { 1 }
}
pub trait OneIfSomeOption {
fn one_if_some(&self) -> usize;
}
impl<T> OneIfSomeOption for Option<T> {
fn one_if_some(&self) -> usize { self.is_some() as usize }
}
macro_rules! generate {
($name:ident {$($field:ident : $t:ty),+}) => {
struct $name { $($field: $t),+ }
impl $name {
fn field_count(&self) -> usize {
#[allow(unused_imports)]
use $crate::{OneIfSomeGeneral, OneIfSomeOption};
generate!(#count self $($field,)*)
}
}
};
(#count $self:ident $first_field:ident, $($rest:ident,)*) => {
(&$self.$first_field).one_if_some()
+ generate!(#count $self $($rest,)*)
};
(#count $self:ident) => { 0 };
}
(Note: There is a problem with the above code: if the field type has a method named one_if_some(), it will take priority. So choose an unambiguous name (__my_generate_macro__one_if_some(), perhaps?)).

Pattern binding the same variable to different types sharing a trait

I have a question about pattern matching on values sharing some behaviour through a trait.
I have an enum with two variants, each binding value of different types, where both types implement a trait. I'm trying to figure out whether it's possible to create a single pattern (of the E::VarA(x) | E::VarB(x) form) in which I bind both types to a single constant, provided I'm only interested in using the shared behaviour.
An illustrative example: Playground:
trait T {
fn f(&self) -> usize;
}
struct A;
impl T for A {
fn f(&self) -> usize { 1 }
}
struct B;
impl T for B {
fn f(&self) -> usize { 2 }
}
enum E {
VarA(A),
VarB(B),
}
fn unwrap(e: E) -> usize {
match e {
E::VarA(v) | E::VarB(v) => T::f(&v)
}
}
fn main() {
let val = E::VarA(A{});
println!("{}", unwrap(val));
}
The code obviously does not compile, but it shows my intentions. Is there a way to make the code work, preferably more elegant than simply splitting the pat1 | pat2 => ... into pat1 => ... ; pat2 => ... ?
You can make a macro that unwraps to match statement.
trait T {
fn f(&self) -> usize;
}
struct A;
impl T for A {
fn f(&self) -> usize { 1 }
}
struct B;
impl T for B {
fn f(&self) -> usize { 2 }
}
enum E {
VarA(A),
VarB(B),
}
macro_rules! unwrap {
($value:expr, $pattern:pat => $result:expr) => {
match $value {
E::VarA($pattern) => $result,
E::VarB($pattern) => $result,
}
};
}
fn main() {
let a = E::VarA(A{});
let b = E::VarB(B{});
println!("a:{} b:{}",
unwrap!(a, ref sm => sm.f()),
unwrap!(b, ref sm => sm.f()));
}
If all variants implement this trait, the best solution is to implement the trait for the whole enum (playground).
Relevant code:
impl T for E {
fn f(&self) -> usize {
match self {
E::VarA(x) => x.f(),
E::VarB(x) => x.f(),
}
}
}

How do I alias the type of self in a struct method?

If I have a small struct Test:
struct Test<T> { a: T }
And I wish, in a method of Test to refer to its full type:
impl<T> Test<T> {
fn new(a: T) -> Test<T> {
type X = Test::<T>;
println!("{}", std::intrinsics::type_id::<X>());
Test { a: a }
}
}
This fails with expected ident, found <, and the following fail too:
type X = Test;: wrong number of type arguments: expected 1, found 0 [E0243]
type X = Test<T>;: can't use type parameters from outer function; try using a local type parameter instead with a note use of undeclared type name T
Actually, it makes sense that the former two are rejected; the latter however is slightly more mysterious.
This came about in trying to implement an offset_of! macro: offset_of($T:ty, $field:ident); the macro works rather well, however ty does not accept Test<T> (but accept a parameter-less alias).
Is there any way to either:
craft an alias to the type of self, even in generics?
or, alternatively, make it so that the macro accepts Test<T> as a "type" argument?
Note: I would prefer a solution to the former, if possible, as aliases are really handy.
For reference, here is the offset_of macro I crafted:
macro_rules! offset_of(
($T:ty, $field:ident) => {
unsafe {
let exemplar: $T = std::mem::uninitialized();
let base: *const u8 = std::mem::transmute(&exemplar);
let attr: *const u8 = std::mem::transmute(&exemplar.$field);
std::mem::forget(exemplar);
(attr as isize) - (base as isize)
}
}
);
I may be misunderstanding you, but there already is an alias for the type of self — Self:
#![feature(core)]
struct Test<T> { a: T }
impl<T> Test<T> {
fn new(a: T) -> Test<T>
where T: 'static
{
println!("{}", unsafe { std::intrinsics::type_id::<Self>() });
Test { a: a }
}
}
fn main() {}
I had to add the feature gates, make T 'static to satisfy type_id, and add an unsafe block. I hope that none of that seems suspicious. This seems to work with your alias, as well:
macro_rules! offset_of(
($T:ty, $field:ident) => {
unsafe {
let exemplar: $T = std::mem::uninitialized();
let base: *const u8 = std::mem::transmute(&exemplar);
let attr: *const u8 = std::mem::transmute(&exemplar.$field);
(attr as isize) - (base as isize)
}
}
);
struct Test<T> { a: T, b: T, c: T }
impl<T> Test<T> {
fn new(a: T) -> Test<T>
where T: Copy
{
println!("{}", offset_of!(Self, a));
println!("{}", offset_of!(Self, b));
println!("{}", offset_of!(Self, c));
Test { a: a, b: a, c: a }
}
}
fn main() {
Test::new(1u16);
}

Is it possible to generate a struct with a macro?

trait Print {
fn print(&self);
}
struct Printer {
p: Box<Print>,
}
fn main() {
let i = 1i32;
let s = String::from_str("Hello");
// let mp = make_printer!(i,s);
let mp = |i: i32, s: String| {
struct Foo {
i: i32,
s: String,
}
impl Foo {
fn new(i: i32, s: String) -> Foo {
Foo { i: i, s: s }
}
}
impl Print for Foo {
fn print(&self) {
println!("{} {}", self.i, self.s);
}
}
Printer { p: box Foo::new(i, s) }
};
let printer = mp(i, s);
printer.p.print();
}
I want to create the make_printer! macro which should expand to mp if I call it with make_printer!(i,s).
Is this is possible? I think the biggest issue here is that I need something like decltype from C++ so that I can extract the type from a variable. The struct could then look like:
#![feature(macro_rules)]
macro_rules! make_printer(
($($element:ident),*) => (
struct Foo{
$($element : decltype($element),)*
}
)
)
No, it's not possible to do it with just the expression identifiers. You need explicit types, e.g.
macro_rules! make_printer {
($($element: ident: $ty: ty),*) => {
struct Foo { $($element: $ty),* }
}
}
called like make_printer!(x: i32, y: String).
This is required for two, mostly orthogonal, reasons:
macros don't have access to any type information, they are just local syntactic transformations (e.g. it's not even possible to write a macro (either macro_rules! or procedural) that can tell if a given ident refers to a local variable).
Rust doesn't have a decltype equivalent.

Resources