Partial Borrows and macros (wut?)


#1

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

  1. looks dirty
  2. 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?


#2

this can’t be done because macros happen before types.

you may wanna look at the “partially initialized types” threads.


#3

this can’t be done because macros happen before types.

Any good reason it can’t be implemented/?


#4

What @Soni is referring to is that macros are expanded before anything else, so they can only rely on syntax. @Soni then refers to “partially initialized types” because that is a Pre-RFC that would solve your underlying problem.


Your example with Foo is solvable with some lifetime annotations.

struct Foo {
	arr: Vec<u64>,
}

impl Foo {
	fn divs_iter<'a>(&'a self, divs: &'a [u64]) -> impl Iterator<Item=(&'a u64,&'a u64)> {
		self.arr.iter().zip(divs).filter(|(x,y)| **x % **y == 0)
	}
}

The reason your code didn’t work is because of lifetime elison rules, due to this impl Iterator<...> and Item gets the lifetime of selfwhich could be longer than the lifetime of divs, but we can fix that with some annotations.

playground


#5

I’m a they, also macros can create types (thanks to them being awfully unhygienic) so there’s always the problem of depending on a type you’re defining


#6

Wait,how is the ability to create types in a macro unhygienic?You can take the identifier of the type you are generating as a parameter.Maybe you meant “always creates a type with a fixed name that escapes the macro”?