Return a Rust tuple of two Types such that the field of the second type references the field of the first type

I'm trying to use const generics to do compile-time assertion that a value of a type is used at most some usize (e.g. 5, 27, 267) times, let's call it Counter<T, const MAX: usize, const N: usize=0>.

The type is also a wrapper of another inner type, T.

To gain guarded access to the inner type, I made another struct as the guard, CounterGuard<T, const N: usize>.

I have a method count_up(self, value: T) of Counter to return a tuple of Counter and CounterGuard, Counter is returned so that another count_up can be called to get a value of CounterGuard and CounterGuard is purely for accessing the value.

The field value of CounterGuard is referencing the field value of the corresponding Counter value.

Here is the full code:

#![feature(generic_const_exprs)]

struct Counter<T, const MAX: usize, const N: usize=0> {
    value: T
}

struct CounterGuard<'a, T, const N: usize> {
    value: &'a T
}

impl<T, const MAX: usize> Counter<T, MAX> {
    fn new(value: T) -> Counter<T, MAX> {
        Counter {
            value
        }
    }
}

impl<T, const MAX: usize, const N: usize> Counter<T, MAX, N> {
    const N_MUST_NOT_EXCEED_MAX: () = assert!(N < MAX, "`N` must not exceed `MAX`");
    fn count_up<'a>(self, value: T) -> (Counter<T, MAX, { N + 1 }>, CounterGuard<'a, T, { N + 1 }>) {
        let _ = Self::N_MUST_NOT_EXCEED_MAX;
        let counter = Counter {
            value: value
        };
        let counter_guard = CounterGuard {
            value: &counter.value
        };
        (counter, counter_guard)
    }
}

fn main() {
    let counter: Counter<i32, 2>  = Counter::new(69);
    let (counter, counter_guard) = counter.count_up(70);
    let (counter, counter_guard) = counter.count_up(71);
    let (counter, counter_guard) = counter.count_up(72);
}

Playground

I have no problems understanding the errors, which are:

error[E0597]: `counter.value` does not live long enough
  --> src/main.rs:27:20
   |
23 |         let counter = Counter {
   |             ------- binding `counter` declared here
...
27 |             value: &counter.value
   |                    ^^^^^^^^^^^^^^ borrowed value does not live long enough
...
30 |     }
   |     -- borrow later used here
   |     |
   |     `counter.value` dropped here while still borrowed

error[E0505]: cannot move out of `counter` because it is borrowed
  --> src/main.rs:29:10
   |
23 |         let counter = Counter {
   |             ------- binding `counter` declared here
...
27 |             value: &counter.value
   |                    -------------- borrow of `counter.value` occurs here
28 |         };
29 |         (counter, counter_guard)
   |          ^^^^^^^  ------------- borrow later used here
   |          |
   |          move out of `counter` occurs here

However, my question is, how do I write a tuple of types such that the field of one of the types references to a field of the other types?

I think that falls into the category of self-referencing types. This thread has more info and suggestions.

2 Likes

Since this could be an XY question, can you explain why you want to return the updated counter by value, rather than mutate it using an &mut method? If you mutated it, then a guard could be returned that references the mutable counter (and does not contain a new counter).

1 Like

The TL;DR is that it's not possible, because it's wrong, specifically it's unconditionally a use-after-free error. Returning a value is a move (it can involve copying from the callee's stack frame to that of the caller), so any reference you create to the value to be returned will be invalid by the time the function will have returned.

Just don't do this.

4 Likes

Thank you for your question.

I wish to track at compile time the number of times the Counter.count_up() is used, hence .count_up() should return a value of Counter with a new generic const argument equals to { EC + 1 } passed in.

I'll guess I will need to pin Counter.

Something like:

Pin<ManuallyDrop<NonNull>>

Or this will probably work, will try it at the latest opportunity.