Finlay Weber
Finlay Weber

Reputation: 4163

Is it possible to know if a variable is a reference at compile time in Rust?

Given the following code:

fn main() {
    let a: i32 = 1;
    let b: &i32 = &a;
    let c: String = "hello world".to_string();
    let d: &str = &c[..];
}

I know

Ok, so now my question is, is there a way to inspect these variables or these types at compile time to know which kind of reference they are?

I know there are couple of functions that can be used to introspect types. Like std::mem::size_of and std::mem::align_of. I was wondering are there ones that can be use to deduce what kind of reference something is?

Upvotes: 1

Views: 793

Answers (2)

Locke
Locke

Reputation: 8954

If you simply want to know what a reference points to, then you can use the ToOwned trait (Ex: <str as ToOwned>::Owned would evaluate to String). However, it sounds to me what you may want is a Cow<'a, T>. Their name stands for Clone On Write. At their core they are simply an enum of either a reference or an owned value.

pub enum Cow<'a, B> 
where
    B: 'a + ToOwned + ?Sized, 
{
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

They can be extremely helpful for operations on strings and slices where you may need to modify the input, but do not want to copy all of the data if the input is already valid. The main benefit is they can be treated as owned values, but give you the flexibility to switch between the two based on performance.

use std::borrow::Cow;

pub fn remove_fives(slice: &[i32]) -> Cow<[i32]> {
    if slice.iter().any(|x| *x == 5) {
        // We found some 5s to remove so we need to clone the input and remove them
        // from the result. This creates a new Vec<i32> we can modify.
        let mut owned_values = slice.to_owned();
        owned_values.retain(|x| *x != 5);
        
        Cow::Owned(owned_values)
    } else {
        // No fives were found so no we don't need to allocate anything or do extra work
        Cow::Borrowed(slice)
    }
}

As a fun side point, if we interpret just the title of the question it is not too difficult to write a simple is_ref<T>() function that is resolved to a value at compile time. It uses traits in a similar manor to the far more helpful impls crate. This can be interesting, but probably is not all that helpful.

pub const fn is_ref<T: ?Sized>() -> bool {
    trait IsNotRef {
        const IS_REF: bool = false;
    }
    
    impl<A: ?Sized> IsNotRef for A {}
    
    struct Wrapper<A: ?Sized>(::std::marker::PhantomData<A>);
    
    impl<'a, A: ?Sized> Wrapper<&'a A> {
        const IS_REF: bool = true;
    }
    
    impl<'a, A: ?Sized> Wrapper<&'a mut A> {
        const IS_REF: bool = true;
    }
    
    <Wrapper<T>>::IS_REF
}

Upvotes: 1

Aleksander Krauze
Aleksander Krauze

Reputation: 6071

It seems, that you don't quite understand what references are in rust-land. Your description suggest that you have a very similar understanding of what a reference is in C++, but here in rust it's all very different.

In rust references are types. T, &T and &mut T are three different types. They are similar to pointers, but have additional guarantees, of which most important are:

  • they are never null
  • they do not outlive data to which they point to
  • there can be only one mutable reference or any number of shared references to a given object at one time

You can have also references to other references (just like in C you can have a pointer to a pointer), so T, &T, &&T, &&&T, etc. are different types.

Now when we say that something is a reference it means that it's type is some kind of reference. So in the following code

fn main() {
    let a: i32 = 1;
    let b: &i32 = &a;
    let c: String = "hello world".to_string();
    let d: &str = &c[..];
}
  • a has a type i32 (a singed integer)
  • b has a type &i32 and thus it's a reference to a number
  • c has a type String and it is not a reference. Internally it contains a pointer to heap allocated buffer of course, but type of String is just String, not a reference to any other type
  • d has a type &str and is a reference. More specifically it is a reference to a string slice. As you pointed out it is a "fat" pointer, because it contains not only the address of the beginning of a slice, but also it's length.

So finally addressing your question. There are only two kinds of references in rust, shared &T and exclusive (or mutable) &mut T. Since all types must be know at the compile time you don't have any runtime introspection or reflection functions that would tell you what kind of a reference is some type. There is not such thing as "smart references" in rust, but there is something called smart pointers, although it is something very different.

Upvotes: 5

Related Questions