2
votes

I am learning Rust and, for the sake of exercise, trying to implement an Instrumented<T> type which:

  • Has a value field of type T;
  • Supports all basic operations that T supports, for example, equality, ordering, arithmetic;
  • Delegates all these operations to T and counts invocations;
  • Prints a nice report.

The idea is borrowed from a programming course by Alexander Stepanov, where he implements this thing in C++. Using Instrumented<T>, one could easily measure the complexity of any algorithm on type T in terms of basic operations, in a very generic way, taking advantage of Rust's trait system.

For start, I am trying to implement a non-generic InstrumentedInt with Add trait and count for all additions in additions field. Full code: http://is.gd/AnF3Rf

And here is the trait itself:

impl Add<InstrumentedInt, InstrumentedInt> for InstrumentedInt {
    fn add(&self, rhs: &InstrumentedInt) -> InstrumentedInt {
        self.additions += 1;
        InstrumentedInt {value: self.value + rhs.value, additions: 0}
    }
}

And of course it does not work because &self is an immutable pointer and its fields cannot be assigned to.

  • Tried to declare arguments of add function mutable, but compiler says it is incompatible with trait definition.
  • Declared add in impl InstrumentedInt without Add trait, this does not provide support for addition (no duck typing).
  • Defined operands as mutable, that gives no effect.

So is this at all possible?

P. S. Afterwards, I'm going to:

  • replace additions with a pointer to a mutable array of uint, to count for many operations, not only addition;
  • make this pointer shared among all instances of InstrumentedInt, maybe just supply an argument in a constructor,
  • generalize the type to Instrumented<T>;
  • ideally, try to make implicit and distinct counters for different specializations. For example, if an algorithm uses both int and f32, Instrumented<int> and Instrumented<f32> should have different counters.

Maybe these are important for the solution of my current problem.

Thank you!

1

1 Answers

3
votes

This is exactly the use case for various kinds of cells. Cells provide facilities to implement interior mutability, i.e. mutability behind & reference. For example, in your case InstrumentedInt could look like this:

struct InstrumentedInt {
    value: int,
    additions: Cell<uint>
}

impl Add<InstrumentedInt, InstrumentedInt> for InstrumentedInt {
    fn add(&self, rhs: &InstrumentedInt) -> InstrumentedInt {
        self.additions.set(self.additions.get()+1);
        InstrumentedInt {value: self.value + rhs.value, additions: Cell::new(0)}
    }
}

As for things that you're going to do afterwards, you may have difficulties implementing them. They seem possible to me to implement, but not easy. For example, you won't be able to use mutable global data without unsafe (it's probably possible to use synchronization tools like Arc and Mutex to use global variables without unsafe, but I've never done it so I don't now for sure). As for different counters for different specializations, you may be able to use libraries like AnyMap or TypeMap, again, with some kind of global variable.