Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Probably, with a slightly tricky ownership issue leading to use after free (a well known source of exploits http://cwe.mitre.org/data/definitions/416.html) e.g. allocate to the heap, pass to a function which deallocates it (assuming that it has ownership) and use it after the call, e.g.

    #include <stdlib.h>
    #include <stdio.h>


    void destroyer(int* val) {
      printf("%d\n", *val);
      free(val);
    }

    int main(int argc, char** argv) {
      int* v = malloc(sizeof(int));
      *v = 3;
      destroyer(v);
      printf("%d\n", *v);
      return 0
    }
Which compiles without warnings using Clang (unless -Weverything, and even then the warnings are not related to use-after-free), works "correctly" in O0 and O1 (prints "3" twice) then breaks starting at O2 (prints "3" then "0"). (note: it always prints "3" twice with GCC 4.8, showing how fun these things are)

meanwhile the equivalent

    fn main() {
        let v = ~3;
        destroyer(v);
        println!("{}", *v)
    }

    fn destroyer(val: ~int) {
        println!("{}", *val)
    }
refuses to compile and explains why:

    test.rs:4:20: 4:21 error: use of moved value: `v`
    test.rs:4     println!("{}", *v)
                                  ^
    note: in expansion of format_args!
    <std-macros>:224:8: 224:50 note: expansion site
    <std-macros>:223:4: 225:6 note: in expansion of format!
    <std-macros>:241:45: 241:63 note: expansion site
    <std-macros>:240:4: 242:5 note: in expansion of println!
    test.rs:4:4: 5:1 note: expansion site
    test.rs:3:14: 3:15 note: `v` moved here because it has type `~int`, which is non-copyable (perhaps you meant to use clone()?)
    test.rs:3     destroyer(v);
                            ^
    error: aborting due to previous error
which can be fixed either by explicitly cloning the value, or by altering the sub-function to not consider it owns the pointer (or by removing the `println!` call in `main()`, thus transferring the ownership of the pointer to the sub-function safely, of course)


Analogous example in C++11, using unique_ptr (which Rust's owned pointer models):

    #include <stdio.h>
    #include <memory>

    void destroyer(std::unique_ptr<int> x)
    {
        printf("%d\n", *x);
        // x is deallocated here
    }

    int main()
    {
        auto x = std::make_unique<int>(3);
        // destroyer(x); ERROR: ownership must be transferred explicitly
        destroyer(move(x)); // Override with an explicit move
        printf("%d\n", *x); // Guaranteed to segfault
        return 0;
    }
This is somewhat more helpful, but sadly the compiler cannot monitor the state of x at compile time, like Rust. The upside is that this bug is easy to catch since the invalid access is guaranteed to try to access a null pointer, and will crash instead of giving garbled results.


> The upside is that this bug is easy to catch since the invalid access is guaranteed to try to access a null pointer, and will crash instead of giving garbled results.

GCC and LLVM optimize based on the assumption that null pointers are never dereferenced, so this is actually undefined behavior, no? Anything can happen.


You're right that it's UB, good point. It's hard to think what a sane compiler would do in this case besides going through with the dereference, though.


A pointer dereference allows the compiler to assume that a pointer is non-NULL, so it can then perform "invalid" optimisations (like dead-code removal), this post contains an very small example: http://blog.llvm.org/2011/05/what-every-c-programmer-should-...


Does `unique_ptr` incur a run-time peanalty for those checks?


For all intents and purposes no. unique_ptr is nothing more than a pointer container that disables copying but allows moving. Therefore only one unique_ptr should be owning a pointer at any given time (unless, like everything in C++, you go around it).


Thanks so much. I'll certainly have to use this if this makes it to Rust proper.


The downside of picking subtler, more intricate examples is that you waste your reader's time trying to understand the subtleties of the example, which isn't teaching them anything about Rust.

Another option is to say something like, "For the sake of brevity, this is a very simple and arguably obvious violation of safety. In practice, there are many subtle and hard-to-diagnose sources of unsafety in C++, even when you use safer abstractions like shared_ptr." This allows you to avoid getting sidetracked and losing your reader, while heading off skepticism of readers with more knowledge about C++.


Your C example is caught by the clang static analyzer:

  main.c:14:20: warning: Use of memory after it is freed
    printf("%d\n", *v);
                   ^~




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: