Reputation: 4163
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
a
is well a normal variable that holds a value,b
is a reference that holds a pointerc
is a reference, although a smart one, because apart from holding the pointer it contains other data like length, capacity, etc.d
is a reference, not a smart one, but what I see often referred to as fat pointer. It contains the pointer and simple extra info like length of the data pointed to.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
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
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:
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 numberc
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 typed
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