Repository: Manishearth/rust-gc (Rust)

Issue:
Add `Gc::as_ptr` by analogy with `Rc::as_ptr`
For context, I'm working on a bytecode interpreter for the [Lox language](https://craftinginterpreters.com/). In my implementation, I'm currently using `Rc`, but I ultimately would like to use a real GC instead. 

There is a part of my code where I rely on `Rc::as_ptr` to get the raw pointer in order to compare memory addresses, roughly like this:

```rust
if Rc::as_ptr(&x) > Rc::as_ptr(&y) {
    // Do stuff.
}
```

I need this to check which value was older when traversing a linked list. 

I didn't find a similar associated function for `Gc`, and I don't know of another way of accomplishing this without it.

It might also be useful to have for other purposes. 

If we agree that this would be a useful function to have, I would be happy to try implementing it.


Discussion:
Yeah I'd accept a PR adding this

However, please note that pointer comparison does not guarantee a pointer is "older"
> I didn't find a similar associated function for `Gc`, and I don't know of another way of accomplishing this without it.

`Gc::as_ptr` is a fine addition, but for the record, you can [cast any reference to a pointer](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer) in safe Rust (`my_gc.as_ref() as *const _` or `&*my_gc as *const _`). One might want to avoid that path in some cases if creating the reference itself would be undefined behavior (if, say, the pointer might be null or misaligned or the wrong type), but that’s not a concern here.

And to second Manishearth’s warning: Rust does not allocate memory in increasing address order, since for example freed memory will be reused.
@andersk Thanks for pointing that out! I'd forgotten about casting references to pointers. I don't often use `as` in Rust when there are alternatives, but you're right, that would likely work here.

As for the warning on memory address order -- I'll note that. If you're curious, the actual code is [here](https://github.com/anna-hope/rusty-lox/blob/5190202e525a13b7946ae3b21b8c9cdada763eda/rlox_lib/src/vm.rs#L360) and [here](https://github.com/anna-hope/rusty-lox/blob/5190202e525a13b7946ae3b21b8c9cdada763eda/rlox_lib/src/vm.rs#L389). It's a pretty straight port of C code from the book, though it seems C doesn't guarantee memory address order either. Perhaps there is something else to this logic that I'm not understanding, because it works in my implementation (and obviously in the C code from the book) -- unless I'm not using complex enough test cases.

@Manishearth I've submitted a PR with the addition.
If you're implementing a GC from a book I suspect the book is providing a deliberately naïve implementation.

> because it works in my implementation (and obviously in the C code from the book) -- unless I'm not using complex enough test cases

Yes, it'll take a while for an allocator to eventually cycle around here.
> If you're implementing a GC from a book I suspect the book is providing a deliberately naïve implementation.

To clarify, it's the implementation of closing upvalues for closures that uses the pointer comparison. 

The book provides its own implementation of a GC, but I am not attempting that just yet, which is why I wanted to use this crate. Anyway, thanks for merging the PR so quickly!
I haven’t tried to read your code, but depending on its implementation details, a [*compacting* garbage collector](https://en.wikipedia.org/wiki/Mark%E2%80%93compact_algorithm) may be able to maintain a guarantee that addresses are ordered chronologically, with the tradeoff that the addresses change during compaction. (This library is not compacting.)



Patch:
diff --git a/gc/src/lib.rs b/gc/src/lib.rs
index 9bbcd31..17ef897 100644
--- a/gc/src/lib.rs
+++ b/gc/src/lib.rs
@@ -110,6 +110,24 @@ impl<T: ?Sized> Gc<T> {
     pub fn ptr_eq(this: &Gc<T>, other: &Gc<T>) -> bool {
         GcBox::ptr_eq(this.inner(), other.inner())
     }
+
+    /// Provides a raw pointer to the data.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use gc::Gc;
+    ///
+    /// let x = Gc::new("hello".to_owned());
+    /// let y = x.clone();
+    /// let x_ptr = Gc::as_ptr(&x);
+    /// assert_eq!(x_ptr, Gc::as_ptr(&y));
+    /// assert_eq!(unsafe { &*x_ptr }, "hello");
+    /// ```
+    pub fn as_ptr(this: &Gc<T>) -> *const T {
+        let ptr = this.inner_ptr();
+        GcBox::value_ptr(ptr)
+    }
 }
 
 /// Returns the given pointer with its root bit cleared.


Test patch:
diff --git a/gc/tests/gc_semantics.rs b/gc/tests/gc_semantics.rs
index ca6d50c..3934d38 100644
--- a/gc/tests/gc_semantics.rs
+++ b/gc/tests/gc_semantics.rs
@@ -289,3 +289,31 @@ fn ptr_eq() {
     assert!(!Gc::ptr_eq(&b.0, &b2.0));
     assert!(!Gc::ptr_eq(&b.0, &a2));
 }
+
+#[test]
+fn as_ptr() {
+    #[derive(Finalize, Trace)]
+    struct A;
+
+    #[derive(Finalize, Trace)]
+    struct B(Gc<A>);
+
+    let a = Gc::new(A);
+    let aa = a.clone();
+    let a_ptr = Gc::as_ptr(&a);
+    assert_eq!(a_ptr, Gc::as_ptr(&aa));
+
+    let b =  Gc::new(B(a.clone()));
+    assert_eq!(a_ptr, Gc::as_ptr(&b.0));
+    let bb = Gc::new(B(a.clone()));
+    assert_eq!(a_ptr, Gc::as_ptr(&bb.0));
+
+    let a2 = Gc::new(A);
+    let a2_ptr = Gc::as_ptr(&a2);
+    assert_ne!(a_ptr, a2_ptr);
+    let b2 = Gc::new(B(a2.clone()));
+    assert_eq!(a2_ptr, Gc::as_ptr(&b2.0));
+    assert_ne!(a_ptr, Gc::as_ptr(&b2.0));
+    assert_ne!(Gc::as_ptr(&b.0), Gc::as_ptr(&b2.0));
+    assert_ne!(Gc::as_ptr(&b.0), a2_ptr);
+}