Require that type NOT be a reference

pub trait SomeTraitT {}

pub struct Foo<'a, T: SomeTraitT + is_not_a_ref> {
  data: Vec<&'a T> }

Is there a way in rust to enforce the is_not_a_ref ? The reason here is that I am okay with

pub struct Dog {}

data: Vec<& Dog> // okay with this
data: Vec<&& Dog> // want to avoid this
data: Vec<&&& Dog> // also want to avoid this

The problem here is that with type inference, if it infers a & Dog instead of a Dog, it might end up creating a data: Vec<&& Dog> which I am trying to avoid.

T: 'static rules out any reference other than &'static Dog, which is pretty rare. The downside is that type inference can still infer a reference and you'll get a rather cryptic error in that case.

2 Likes

You can have a 'static bound, which will prevent temporary references, but not &'static. I'm not sure this is what you want though. (I don't understand what you're aiming for, really.)

I have some vague notion there may be a relation with blanket deref bounds, but it smells a bit like negative bounds.

I agree that my original question was not very clear. Let me try to motivate this. Suppose we have a struct Array (which is basically a Vec), and the following:

Array<T> -> Array<&T>
Array<T> -> Array<T>
Array<T> -> Array<U>

Things like Array<&&T> are silly because when we try to deref, we have to read multiple places in memory to get to the T -- and I would like a way to "ban" the construction of things like Array<&&T>.

I'm okay with Array<T> -- just things laid out in memory.
I'm also okay with Array<&T> (suppose we do a filter, but T is big and we don't want to copy).
Array<&&T> is silly and I would like to ban this.

Does this better motivate the question?

To relate this back to the original question, one strategy for this is:

ArrayV<T: can_not_be_ref> {
  data: Vec<T>
}

ArrayR<'a, T: can_not_be_ref> {
  data: Vec<&'a T>
}

this would allow Vec<T> and Vec<&T> while banning Vec<&&T>

Yeah, I think I get it now. It's an interesting restriction. 'static may be good enough, in a practical sense. Anything more precise I've considered so far doesn't really work because a generic T could be a reference (or smart pointer).

This is usually not a good idea, because you don't know up front what someone is using this for or how desperately they need to do what you're trying to ban. And even if you did ban references, you've still got raw pointers, Box, and Arc et al causing the exact same problems. If you somehow forbid all of those, then users will stick usize in there and go look things up in an array elsewhere, which is also an inefficient double indirection.

5 Likes

This is an internal API where I am designer & user.

  1. I don't forbid references. Vec<&T> is fine. I just don't want a chain of functions auto generating a Vec<&&&&&&&&&T>.

  2. I'm also not trying to prevent multiple levels of indirection from the program. I just want to prevent them from accidentally happening_ due to chaining a bunch of iter / map / filter / scan's.

I don't know about you, but chain a few iter functions together, and I often unintentionally get &&T at places I'm expecting a &T. This is the problem I am trying to solve.

Can't say that's a problem I've ever had. If you take ownership of your input argument you can't return a reference to it, so that should be the way you handle that. I'd like to see an example of your wrapping functions, because they're maybe doing something wrong.

Suppose we have:

group_by_0: (Vec<T>, Fn(&T) -> K) -> HashMap<K, Vec<T>>
group_by_1: (Vec<T>, Fn(&T) -> K) -> HashMap<K, Vec<&T>> // note the ref
filter_0: (Vec<T>, Fn(&T) -> bool) -> Vec<T>
filter_1: (Vec<T>, Fn(&T) -> bool) -> Vec<&T> // note the ref

group_by_1 followed by filter_1 would result in a Vec<&&T>

So I think you could avoid that by defining them like this:

query(Vec<T>) -> Vec<&T>
group_by_0(Vec<T>, Fn(&T) -> K) -> HashMap<K, Vec<T>>
group_by_1(Vec<T>, Fn(&T) -> K) -> HashMap<K, Vec<T>>
filter_0(Vec<T>, Fn(&T) -> bool) -> Vec<T>
filter_1(Vec<T>, Fn(&T) -> bool) -> Vec<T>

Then you could call query to get Vec<&T> in results, or not, and take owneship of them.

Your 'solution' is equivalent to deleting group_by_1 / filter_1, which removes the possibility of doing a group-by w/o doing Clone/Copy.

I can't tell if you are serious or trolling.

I guess you know better than I then. I'll leave you to it.

3 Likes

You cannot, in the first place, define functions with the signatures of group_by_1 and filter_1 because they appear to return references to their input arguments.

Note that in Iterator::filter, which is the most common way of obtaining a &&T by "accident", the && only appears inside the closure, and it cannot escape, so you can't continue to accidentally nest references (to &&&, etc.) by adding filters to the same iterator chain. You can get additional levels of references by nesting more things inside the filter closure, but that is more visually complex and impossible to overlook because the inner stuff will be indented more.

1 Like

Good call. I made a mistake on my part. I meant to write:

group_by_0: (&Vec<T>, Fn(&T) -> K) -> HashMap<K, Vec<T>> // invokes copy/clone
group_by_1: (&Vec<T>, Fn(&T) -> K) -> HashMap<K, Vec<&T>> // note the ref
filter_0: (&Vec<T>, Fn(&T) -> bool) -> Vec<T> // invokes copy/clone
filter_1: (&Vec<T>, Fn(&T) -> bool) -> Vec<&T> // note the ref

I made a bad translation, as I initially have something like this:


struct Array<T> {
  data: Vec<T>
}

impl Array<T> {
group_by_0: (&self, Fn(&T) -> K) -> HashMap<K, Array<T>> // invokes copy/clone
group_by_1: (&self, Fn(&T) -> K) -> HashMap<K, Array<&T>> // note the ref
filter_0: (&self, Fn(&T) -> bool) -> Array<T> // invokes copy/clone
filter_1: (&self, Fn(&T) -> bool) -> Array<&T> // note the ref

The issue here is: when we do a group_by/filter, we have to pay one of two costs:

  1. don't copy, but incur indirection in future
  2. copy, but no indirection in future

The issue I am trying to avoid is multiple layers of indirection like an internal Vec<&&&&&&T>; I want to cap the indirection at one level.

What if the functions took not simply &T, but instead U: AsRef<T>? AsRef docs

Also, here is a very interesting discussion about reference chains that might help confuse things further.

The compiler distinguishes between references and non-references in it's * (dereference) implementation. But I couldn't figure out a way to exploit this. Interesting side-note: You can dereference a &T in const context, but not utilize Deref::deref, which at least exposes the difference.

Here's an abuse of API which could break at any time that supplies a run-time check. (It could be a compile-time check/trait once const is strong enough.)

Off to seek penance for me.

1 Like

In classic fashion, after thinking about this for a minute after posting I realized that it probably can be exploited with a macro. I have to leave for now; perhaps someone else will get to it before I try.

Macro expansion happens before type checking right? So it seems, if we were to do this at compile time, this would be some type of "useless (at runtime)" const expr where depending on the value of std::any::type_name::(); , we either get a type error (say dimension mismatch) or everything goes smoothly?

I was thinking of something that didn't depend on type_name, something like a const function or other context that fails when given a type that's not shallow enough. You can't have a generic const function on stable, and if you have a generic function the references get hidden behind the type parameter anyway. But you can sometimes side-step those limitations with macros.

Probably still pretty limited, if possible, because the case that breaks under const is when Deref must be used (or implemented) because you don't have a reference; it's limited in the wrong direction. So it might have to be in a must-fail test block or something. And supposedly some day the const limitation will go away.