In C, a pointer can point to either heap or stack. How to do the same in Rust?

In C, a ptr variable can be used to reference either stack content or heap content:

int *ptr = &5;
ptr = malloc(size_of(int));

In Rust, we have regular references for stack content and Box for heap content. Say I have a struct with a pointer field to a HashMap that may be allocated either on the stack on the heap, how would I type this struct and initialize it?

My guess was this:

pub struct Struct<'a> {
   hm_ptr: &'a HashMap<&'a str, &'a str>
}

impl<'a> Struct<'a> {
    pub fn new() -> Self {
        Self {
            hm_ptr: Box::new(HashMap::new()).as_ref(),
        }
    }

   pub fn swap(&mut self, new_hm_ptr: &'a HashMap<&'a str, &'a str>) {
       self.hm_ptr = new_hm_ptr; // potential stack reference
   }
}

But this won't work since Struct::new returns a value referencing data it owns.

Regular references can point to the stack, the heap, static memory, wherever.

This is more a question of ownership than memory location IMO. Perhaps you want a Cow<'a, HashMap<K, V>>.

3 Likes

The way you would type and initialize any other type.

The stack vs. heap distinction simply doesn't exist at the type system level in the language. (Neither does it in C, by the way.)

1 Like

In C, a pointer describes only how to access a value. The programmer is responsible for keeping track of who owns that value, and deciding whether and how to dispose of it once it's no longer in use.

Rust, on the other hand, tracks both through the type system. A reference describes how a value is accessed; a Box (or a bare variable or field, or an Rc/Arc, or any of a number of other types) describes how it is allocated. The rules of the language are designed to ensure that you can only take a reference to a value while that value still exists, and to ensure that you can't dispose of a value (dropping it or moving it) while there are references outstanding.

The program you're trying to write defines a type Struct, which will own a HashMap when initially created, but which may also borrow a hashmap someone else owns. That is possible, but it's a bit clunky, and I would try to avoid that definition if possible: decide whether Struct should always borrow a hashmap, or should always own a hashmap, and go from there, if you can.

If you must, for whatever reason, allow both owned and borrowed values, then you want the std::borrow::Cow (short for "copy on write") type. This is an enumerated type that has two cases. Simplifying a bit, they are:

  • Cow::<T>::Borrowed(&T), representing a value borrowed from elsehwere, and
  • Cow::<T>::Owned(T), representing an owned value.
use std::borrow::Cow;
use std::collections::HashMap;

pub struct Struct<'a> {
   hm: Cow<'a, HashMap<&'a str, &'a str>>
}

impl<'a> Struct<'a> {
    pub fn new() -> Self {
        Self {
            hm: Cow::Owned(HashMap::new()),
        }
    }

   pub fn swap(&mut self, hm: &'a HashMap<&'a str, &'a str>) {
       self.hm = Cow::Borrowed(hm);
   }
}

One caveat with this approach is that you will have to decide in advance what lifetime bound to pick for 'a. In most cases the compiler can infer it for you, but you still need to ensure that the Struct instance lives no longer than the hashmaps that might be passed to swap do, and lifetime constraints are how Rust does that. This will, as a result, be very awkward to work with.

3 Likes

Thanks for the clarification everyone. This makes sense.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.