13 ms·
My favorite Rust function
- grenoire 7y agoWow, elegant. This was probably conceived as an idea during the design phase of the language, it seems right.
- ratww 7y agoThat same function appears on the second page of Philip Wadler's first Linear Logic paper, but it's called "kill" [1] But I remember the words "drop" and "dup" being used since the early days of linear logic too. I believe they come from Forth, where they do pretty much the same thing! [2] [1] http://homepages.inf.ed.ac.uk/wadler/papers/linear/linear.ps http://homepages.inf.ed.ac.uk/wadler/papers/linear/linear.ps [2] http://wiki.laptop.org/go/Forth_stack_operators http://wiki.laptop.org/go/Forth_stack_operators
- saagarjha 7y agoThere’s a number of fun C++ ones similar in spirit: std::move, for example.
- codeflo 7y agostd::move's implementation not quite as elegant, though (this is from the GCC source): template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
- saagarjha 7y agoIt’s a bit ugly because the standard library functions are replete with underscores, and of course it isn’t empty. But I still think it’s quite surprising, as most people would think it actually does some sort of semantic “move”.
- saghm 7y ago> It’s a bit ugly because the standard library functions are replete with underscores, and of course it isn’t empty. I'll be honest, neither of those are my top issue with that in terms of why I think it's ugly.
- jcelerier 7y agoThe standard C++ way to free resources is the character '}'
- deleted 7y ago[deleted]
- wwright 7y agoRust has the exact same semantics there. Drop is useful when you need to explicitly notate that a value should end its life early.
- klipt 7y agovoid drop(unique_ptr<T>&& x) {} Would do the same in C++ for values held by unique_ptr: take ownership of the pointer and then free it.
- SamReidHughes 7y agoNo, it would not free it. It takes a reference to the pointer and does nothing.
- dkersten 7y agounique_ptr disposes of the object its holding when it goes out of scope unless passed to another unique_ptr or ownership is explicitly released. Presumably klipt would std::move the unique_ptr, hence the universal reference (which seems unnecessary, just pass it by value). Am I missing something? Does it not work with std::move?
- Rusky 7y agoYes, you're missing that the type `T&&` is a reference, not a value, and so does not run the object's destructor when it goes out of scope.
- etxm 7y ago> unacceptably crippled like Go I just spit mezcal on a stranger.
- totalperspectiv 7y agoWere they okay with it once you explained what was so funny?
- etxm 7y agoIt was a friend of a friend. The mezcal coming out of my nose was humorous to them.
- dbieber 7y agoI haven't used rust, so can you explain this to me: If I do the rust equivalent of: def add1(x): return x + 1 x = 1 y = add1(x) z = add1(x) then will x have been deallocated by the first call to add1 and will the second call to add1 fail? [You can ignore the fact that I'm using numbers and substitute an object if that makes more sense in the context of allocating / deallocating memory in rust.]
- saagarjha 7y agoIt depends: if add1 borrows the value then this code is fine. If add1 takes ownership of the parameter then no, this will not work.
- AlanSE 7y agoWhat do you mean by "will not work"? Will it not compile? And how do we write that with add1 taking ownership vs. not?
- db48x 7y agoIt won't compile, because the compiler keeps track of where you moved the value and notices that x now has no value. If you want the function to borrow the argument instead, make it take a reference, &x.
- Filligree 7y agoYou'll get a compile-time error explaining the situation, yes, including documentation links and possible fixes. The differences between the versions are minimal. fn add1(x: Thing) -> Thing { x + 1 } fn add2(x: &Thing) -> Thing { x + 2 } This works on the assumption that whatever "+ 1" really means doesn't itself need ownership of x, e.g. because it mutates it. If it does, then you'll get an error. Or you could do this: fn add3(x: &mut Thing) { x.add(3) } Which, presumably, modifies x instead of returning a copy. It's your job to make sure it makes sense to do that, but Rust has another trick in its pockets: let y = add1(x); // Works; takes ownership. let z = add2(&y); // Works, and doesn't take ownership. let zz = add2(&y); // So you can do it twice. (Y tho?) let h = add3(&y); // Compile-time error! let hh = add3(&mut y); // Works. You need to specify that you're fine with y being mutated.
- gautamcgoel 7y agoGotta admit, that really is a cute example. However, I was a bit surprised when the author described Go as "unacceptably crippled." What is he referring to?
- saagarjha 7y agoLack of a decent type system?
- augusto2112 7y agoProbably lack of generics?
- gautamcgoel 7y agoI wonder if the author would have a more positive impression of Go 2, which is planned to ship with generics...
- Filligree 7y agoSlightly less crippled. One can keep the performance and feature comparisons going for a while. Go shouldn't be Rust. We already have Rust. Though while I say that, I'd still hate to have to use Go.
- TheDong 7y agoThe usual suspects for things missing from go are the following: Generics, sum types, match statements, tuple types, compile-time data-race detection, type-safe concurrent-maps, hygienic macros, immutable types/references, functional constructs such as 'map', 'filter', or monads, marker interfaces, better error handling, type-inference for consts that isn't garbage, etc. Less common complaints are that it's missing: object oriented features like inheritance, a configurable gc (as java has), the ability to work with OS threads, c-compatible stacks for fast c-interop, ownership semantics, type-inference for arguments (e.g. as haskell does), operator overloading, dependent types, etc. The list of things in the first set can mostly be summed up as "go has a worse type-system than C++/rust/etc, something much closer to java 1 before generics, or c". Basically, the language is intentionally crippled because it intentionally ignores advances in type-theory that have been shown to allow expressing many things more safely. For example, sum types and match statements make modifying code much safer. People will write switch/if-else-ladder code to do exactly the same sort of thing even without them, the code will just fail at runtime rather than compile-time when a new variant is added or one is not handled by accident.
- foopdoopfoop 7y agoReminds me of Coq's definition of `False`: `Inductive False := .` i.e., `False` has no constructors and hence is an empty type. Anyway, this means that for any type `A`, you can construct a function of type `False -> A` because you just do this: `fun (x : False) => match x with end.` Since `False` has no constructors, a match statement on a value of type `False` has no cases to match on, and you're done. (Coq's type system requires a case for each constructor of the type of the thing being matched on.) This is why, if you assume something that is false, you can prove anything. :)
- erk__ 7y agoThat is made that way to mirror how logic usually is made to work in propositional logic.
- jchw 7y ago> or making the language unacceptably crippled like Go Gotta say, I lost a lot of respect for the author at this point. It’s not like I don’t love Rust - quite the contrary - but if the only takeaway from Go for you is that it is “unacceptably crippled” then I feel you have missed a lot of insight. Go has been one of my languages of choice for over half a decade now, and for good reason.
- viraptor 7y agoIt may be an unnecessary dig, but the author may indeed be familiar with Go and still think it's unacceptably crippled for all their use cases. The whole post is just their opinion.
- jchw 7y agoSure, and my opinion is that this is a very shallow takeaway. My whole comment is just my opinion.
- mav3rick 7y agoIt is crippled admittedly by the authors themselves. The error checking pattern, no generics etc. are all to keep it "simple" yet are things most other programming languages have. Have you used Rust ? The borrow checker makes a lot of sense.
- jchw 7y agoI feel like I shouldn’t have to answer this question, but Yes, of course I’ve used Rust. Why do you question this? I said nothing about Rust at all, other than that I like it, and whether Rust is good or not has little to do with Go being “crippled.” But of course, Rust is not flawless. In fact up until recently it was kind of annoying, before non-lexical lifetimes became a part of the language. It’s also a very large and complex language compared to Go. This is not unilaterally a bad thing, but just as every line of code comes at a cost, so to does every language feature, and if enough language features fail to pay the rent your programming language will end up feeling bloated. What Go lacks is exactly its strengths. If you criticize C for not having Java-style exceptions, people will look at you funny. There is some divide in the community over error handling but I defend that Go’s verbose and stupid error handling pattern is my favorite part of the language, and changed how I code inside and outside of Go. I also appreciate Go’s simple but effective patterns for composition-based object oriented programming, and for having a decent concurrency model (not that it is without flaws: the limitation of Go’s memory safety is definitely an issue here.) Everything comes at a cost. The borrow checker brings immense promise for security, but that does not mean other approaches to memory safety or language design are suddenly obsolete. It’s more complicated than that, and I consider failure to understand this to be a sign that someone is not keeping an open mind.
- hinkley 7y agoDo variables go out of scope after last use or when the function exits? I could see the former evolving into the language if it’s not already the default behavior. In which case there’s only one situation where I could see this useful, and that’s when you are building a large object to replace an old one. The semantics of foo = buildGiantBoject(); In most languages is that foo exists until reassigned. When the object represents a nontrivial amount of memory, and you don’t have fallback behavior that keeps the old data, then you might see something like drop(foo); foo = buildGiantBoject(); Most of the rest of the time it’s not worth the hassle.
- Filligree 7y agoIt used to be at the end of the block, which caused all manner of annoyance. So they spent a lot of effort improving the borrow checker, and now it's 'after last use'. It's not just a matter of memory use. References and mutable references form a sort of compile-time read-write mutex; you can't take a mutable reference without first dropping all other references. See https://stackoverflow.com/questions/50251487/what-are-non-lexical-lifetimes https://stackoverflow.com/questions/50251487/what-are-non-le... for more.
- Rusky 7y agoThis is incorrect. Values still go out of scope and have their destructors run at the same time. NLL only affects values without destructors.
- oconnor663 7y agoI think `#[may_dangle]` is an exception to this, and the standard library puts it on many (most?) container types.
- Rusky 7y agoIt is not an exception; #[may_dangle] does not change the time drop runs. All it does is promise that drop will not access borrowed data, allowing that data to die before drop: https://doc.rust-lang.org/nightly/nomicon/dropck.html https://doc.rust-lang.org/nightly/nomicon/dropck.html
- cztomsik 7y agoFor me, rust is still love & hate, even after 1 year of half-time (most of the free time I have) hacking. It's a wonderful language but there are still some PITAs. For example you can't initialize some const x: SomeStruct with a function call. Also, zero-cost abstraction is likely the biggest bullshit I've ever heard, there is a lot of cost and there's also a lot of waiting for compiler if you're using cargo packages. That said, I wouldn't rather use C/C++/Go/Reason/Ocaml/? - that is probably the love part. BTW: I've recently stopped worrying about unsafe and it got a bit better. So my message is probably: - keep your deps shallow, don't be afraid to implement something yourself - if you get pissed off, try again later (sometimes try it the rust way, sometimes just do it in an entirely different way)
- Kenji 7y ago> Also, zero-cost abstraction is likely the biggest bullshit I've ever heard, there is a lot of cost and there's also a lot of waiting for compiler if you're using cargo packages. Zero cost refers to runtime cost, not compilation cost. Zero cost abstraction is not bullshit.
- cztomsik 7y agoYes, I know, and all those crates trying to wrap unsafe C libs are really not free - I've tried. Maybe just dig into some sources and see what macros are expanded to to see the overhead.
- kam 7y ago> you can't initialize some const x: SomeStruct with a function call. You can if it's a `const fn`. The set of features you can use in `const` is small but growing.
- The_rationalist 7y agoAnother PITA is the lack of safe static variables.
- Rusky 7y ago
- holy_city 7y agoI wouldn't say std::mem::drop acts like free at all, it's the equivalent of a destructor in C++. Mostly useful when you're dealing with manually allocated memory, FFI, implementing an RAII pattern, etc. One cool thing about Drop (and some other cool stuff, like MaybeUninit) is that it makes doing things like allocating/freeing in place just like any other Rust code. There may be some unsafe involved, but the syntax is consistent. Whereas in C++ seeing placement new and manually called destructors can raise eyebrows.
- unexaminedlife 7y agoI get the feeling this doesn't really get into the meat of what "drop" is. It seems you can't really explain why you "love" a function without discussing its purpose. Maybe I'm wrong, I'm only really an outsider looking in when it comes to rust, but it does fascinate me as far as its goals. I would go so far as to say that it will be important for systems programmers to know in the not too distant future (if it's not already). Isn't it really only there in case someone needs to "hook into" the drop functionality before the variable is dropped? Please correct me if I'm wrong. EDIT: Minor editing to clarify meaning.
- steveklabnik 7y agoYes, to do anything interesting, you need to implement the Drop trait, which causes interesting behavior to happen here.
- dmix 7y agoAn example of the implementation is all that's missing from the blog post.
- steveklabnik 7y agohttps://doc.rust-lang.org/stable/std/ops/trait.Drop.html https://doc.rust-lang.org/stable/std/ops/trait.Drop.html
- Rusky 7y agoThe post isn't talking about the drop method of the Drop trait, which is used to hook into the drop functionally. It's talking instead about std::mem::drop, a standard library function that drops a value before it would ordinarily go out of scope.
- millstone 7y agolet x = String::from("abc"); std::mem::drop(&x); std::mem::drop(&x); std::mem::drop(&x); std::mem::drop(&x);
- hathawsh 7y agoFWIW, that won't compile because std::mem::drop requires ownership of the object being passed; your code is trying to pass a reference instead.
- lpghatguy 7y agoThis code compiles just fine. It doesn't do anything since immutable references are Copy, and so dropping them doesn't do anything. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f2b4935fbb248718f1f2878ee4c53409 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- hathawsh 7y agoOh right, I forgot about the interaction with generics in this case. Semantically, I suppose it's creating and dropping a reference 4 times.
- steveklabnik 7y agoThis should compile, &T is Copy and so it will not really do anything, but it will still compile. On my phone so I can’t triple check, but there’s no bound on T, you can pass in any type.
- deleted 7y ago[deleted]
- newacctjhro 7y ago> Now this might seem like a hack, but it really is not. Most languages would either ask the programmers to explicitly call free() or implicitly call a magic runtime.deallocate() within a complex garbage collector. The compiler actually implicitly adds drop glue to all dropped variables!
- lpghatguy 7y agoSometimes Rust programmers write this same function as a closure, which can be known as the 'toilet closure'[1]: |_| () [1] https://twitter.com/myrrlyn/status/1156577337204465664 https://twitter.com/myrrlyn/status/1156577337204465664
- dingo_bat 7y ago> making the language unacceptably crippled like Go As compared to Rust which doesn't let you write a simple doubly linked list. What hubris!
- flywithdolp 7y agocan someone elaborate on this passage: The beauty of programming language design is not building the most complex edifice like Scala or making the language unacceptably crippled like Go - but giving the programmer the ability to represent complex ideas elegantly and safely. Rust really shines in that regard. i'm fairly ignorant on the various differences but my general feeling was that Go is quite useful?
- qznc 7y agoThe usual quip because Go has no Templates/Generics, so you have to sacrifice type safety all the time by casting to and from Interface{}.
- likeliv 7y agoOne of the philosophies behind Go is to keep the language extra simple. See "less is exponentially more". The same way some electric bikes are restricted to a given speed to keep their user safe. Some people call it "crippled", while some other call it "simple and safe to use".
- mlindner 7y agoKeeping a language simple just punts complexity from the language to the implementation that uses that language. This is all the same problems that C has and Go chose to for some reason copy this poor philosophy. It's like Go designers decided most programmers are too dumb to understand complex languages.