I was thinking about the partial borrowing issue lately and found that macro methods(better name) can be a nice addition to the language and can make some stuff less dirty, it might be be a bad idea but I would like to discuss it anyway.
So we have a problem with partial borrowing that looks like this (See also here https://github.com/rust-lang/rfcs/issues/1215)
struct Point {
x: u64,
y: u64
}
impl Point {
pub fn x_mut(&mut self) -> &mut f64 {
&mut self.x
}
pub fn y_mut(&mut self) -> &mut f64 {
&mut self.y
}
}
fn main() {
let mut p = Point{x:1,y:2};
let x = p.x_mut();
- first mutable borrow occurs here
let y = p.y_mut();
^ second mutable borrow occurs here
}
we can ‘solve’ it using macros like this
macro_rules! x_mut {
($point:ident) => (
&mut $point.x
)
}
macro_rules! y_mut {
($point:ident) => (
&mut $point.y
)
}
fn main() {
let mut p = Point{x:1,y:2};
let x = x_mut!(p);
let y = y_mut!(p);
}
And now its working but there are 2 problems
- looks dirty
- x and y macros work on any type
I was thinking to solve those 2 problems by macro methods
impl Point {
macro x(&mut self) -> &mut u64 {
&mut self.x
}
macro y(&mut self) -> &mut u64 {
&mut self.y
}
}
So now we can do this
fn main() {
let mut p = Point{x:1,y:2};
let x = p.x!();
let y = p.y!();
}
Now we solved both of the problems we had with a clean solution and we can still see we are using a macro. This can be useful in other places with trouble borrowing
struct Foo {
arr: Vec<u64>,
}
impl Foo {
fn divs_iter(&self, divs: &[u64]) -> impl Iterator<Item=(&u64,&u64)> {
------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| ...but data from `divs` is returned here
this parameter and the return type are declared with different lifetimes...
self.arr.iter().zip(divs).filter(|(x,y)| **x % **y == 0)
}
}
But we can do this if we just type it in (but wont work in a function)
let foo = Foo{arr: vec![1,2,3,4,5]};
for n in foo.arr.iter().zip(&[1,2,3,4]).filter(|(x,y)| **x % **y == 0) {
println!("{:?}", n);
}
this will work with macro methods like this
impl Foo {
macro divs_iter(&self, divs: &[u64]) -> impl Iterator<Item=(&u64,&u64)> {
self.arr.iter().zip(divs).filter(|(x,y)| **x % **y == 0)
}
}
let foo = Foo{arr: vec![1,2,3,4,5]};
for n in foo.divs_iter!(&[1,2,3,4]) {
println!("{:?}", n);
}
So basicly to allow to ‘inline’ code, the rule of thumb is if something compiles fine it should compile fine if we put it in a macro method, and I think it might solve more types of problematic borrowing nicely.
I’m not sure about the return type but thats how I see it working nicely with stuff and with compiler knowing something about the macro method.
This is different to other solutions because macro methods are not functions they are just sugar.
This might be abused and produce harder to debug errors but if used correctly I can see nice abstractions with code that were impossible to do in a nice way.
Is this even good?