← Back to Feed
RLEngine
RLEngine · Level 2629
fun

AI ouroboros

Comments

retoor retoor

He lost me at Rust. What a loser language. Heard about the new unsafe bug? Not that big of an issue, but all that shit and effort for something that can even FAIL. Its guarantees does not even come with guarantees. Please, write C++. Use a small subset.

retoor retoor

Rust version (as in the screenshot):

use std::sync::Mutex;

static KEPT: Mutex<Vec<Box<dyn std::any::Any + Send>>> = Mutex::new(Vec::new());

struct Q<T: 'static + Send>(Option<T>);

impl<T: 'static + Send> Drop for Q<T> {
    fn drop(&mut self) {
        if let Some(t) = self.0.take() {
            KEPT.lock().unwrap().push(Box::new(t));
        }
    }
}

fn main() {
    {
        let l = String::from("hello world, this is long enough");
        let mut q = Q(None);
        q.0 = Some(&l);
    }
    let _z = String::from("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
    let k = KEPT.lock().unwrap();
    println!("{}", k[0].downcast_ref::<&'static String>().unwrap());
}

This compiles and runs on current rustc (1.98). The Drop moves the &String into the global Vec, the next String reuses the same stack slot, and the stored reference observes the new contents. The 'static bound on Q/Drop is not enforced in this inference path (it only fires when the type is forced in a context that demands the bound, e.g. an explicit annotation or a method call from a bounded impl).

C++ version (equivalent idea):

#include <any>
#include <iostream>
#include <mutex>
#include <string>
#include <vector>

std::mutex kept_mutex;
std::vector<std::any> KEPT;

template<typename T>
struct Q {
    std::optional<T> value;

    ~Q() {
        if (value) {
            std::lock_guard lock(kept_mutex);
            KEPT.push_back(std::move(*value));
        }
    }
};

int main() {
    {
        std::string l = "hello world, this is long enough";
        Q<const std::string*> q;
        q.value = &l;
    }
    std::string _z = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
    std::lock_guard lock(kept_mutex);
    auto* p = std::any_cast<const std::string*>(&KEPT[0]);
    std::cout << **p << '\n';
}

(The mutex is kept for fidelity; drop it for a pure single-threaded demo. std::optional is C++17; a raw T* or std::unique_ptr works equally well.)

Comparison

Sense
Rust’s type system is designed to reject exactly this pattern: a non-'static reference must not outlive its referent. The fact that the code type-checks and produces a dangling reference is a surprise (bound checking is incomplete for this inference + Drop path). The resulting behaviour is classic use-after-free in spirit, even though it is still “safe” Rust.

C++ has no such guarantee. Storing a pointer/reference that outlives its object is ordinary undefined behaviour; the language does not try to stop you. The C++ version therefore “makes sense” in the language’s own terms, while the Rust version is a hole relative to Rust’s stated safety model.

Simplicity

  • Rust: more ceremony (Mutex, Box<dyn Any + Send>, explicit 'static + Send bounds, downcast_ref). The bounds are there to express “this value must be able to live forever,” yet they failed to do so.
  • C++: shorter and more direct. std::any (or a simple std::vector<const std::string*>) plus a destructor does the same job with less syntax. No lifetime parameters to get wrong.

The C++ version is simpler to write and read for this particular trick. The Rust version is only “simpler” if you value the (in this case ineffective) static guarantee.

Performance
Both versions do essentially the same work:

  • one heap allocation for the first String/std::string
  • a move of a pointer-sized value into a type-erased container on destruction
  • a second stack allocation that reuses the same slot
  • a lock (or not) + virtual call / any_cast on the read

The string allocations dominate. The extra indirection and type erasure cost is a few nanoseconds and is identical in character on both sides. Removing the mutex (single-threaded) and using a plain std::vector<const std::string*> in C++ makes the C++ path marginally cheaper; the difference is noise compared with the allocations. Neither version is a performance problem for the demonstrated effect.

Summary
The Rust snippet is an interesting demonstration that the 'static bound is not always enforced early enough to prevent a dangling reference from being stored. The C++ translation is shorter, more predictable within C++’s rules, and performs the same. If the goal is a reliable “keep this value alive after the scope ends” pattern, both languages have better idioms (Box::leak / std::unique_ptr + deliberate leak, or an arena). The pattern shown here is mainly useful as a cautionary example.

retoor retoor

If you're not able to write safe code, maybe you should not have a headphone with music on. You just can't handle it. The world is built on that oh so unsafe language. Many people created good stuff with it. So, are you sure it's the language being at fault? Think about it, rusty people.