
My favorite Rust function - jaseemabid
https://blog.jabid.in/2019/10/11/drop.html
======
foopdoopfoop
Reminds 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__
That is made that way to mirror how logic usually is made to work in
propositional logic.

------
unexaminedlife
I 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
Yes, to do anything interesting, you need to implement the Drop trait, which
causes interesting behavior to happen here.

~~~
dmix
An example of the implementation is all that's missing from the blog post.

~~~
steveklabnik
[https://doc.rust-lang.org/stable/std/ops/trait.Drop.html](https://doc.rust-
lang.org/stable/std/ops/trait.Drop.html)

------
holy_city
I 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.

------
dbieber
I 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.]

~~~
saghm
Interestingly, the type of `x` actually does matter here in Rust! For most
types, yes, passing something by value into a function will cause the memory
to be "moved", which means that reusing `x` will be a compiler error. That
being said, you can also either pass a shared reference (i.e. `&x`), which
will allow you to access the data in Rust (provided you don't move anything
out from it or mutate it, which would cause a compiler error) or a mutable
reference (i.e. `&mut x`), which will allow you to access or mutate the data
in `x` but not take ownership of it (unless it's replaced with something else
of the same type).

However, a few types, including integers, but also things like booleans and
chars, implement a trait (which for the purposes of this discussion is like an
interface, if you're not familiar with traits) called Copy that means that
they should be implicitly copied rather than moved. This means that in the
specific example you gave above, there would not be any error, since `x` would
be copied implicitly. You can also implement Copy on your own types, but this
is generally only supposed to be done on things that are relatively small due
to the performance overheard of large copies. Instead, for larger types, you
can implement Clone, which gives a `.clone` method that lets you explicitly
copy the type while still having moves rather than copies be the default.
Notably, the Copy trait can only be implemented on types that already
implement Clone, so anything that is implicitly copied be can also be
explicitly copied as well

~~~
nerdponx
Is there any kind of compile-time check available for this (e.g. BIG COMPILER
WARNING when you pass-by-value something that lacks Copy)? Seems like a lot of
unsettlingly Python-esque freedom ("read the docs and don't screw up") for a
language like Rust.

~~~
likeliv
It is perfect fine to take by value something that is not Copy to transfer its
ownership.

It is a compiler error to use the value after it was moved. And the compiler
error is quite explicit about how to solve it

For example you can explicitly clone

    
    
        let y = add(x.clone());
        let z = add(x);

~~~
dorfsmay
What would be cases where the add() function need ownership of the x?

What the chance in real life that a function like that cannot simply use a
reference?

In my learning Rust, my life became significantly better and easier when I
started to references to borrow as much as possible.

~~~
likeliv
That was just an example. But maybe 'x' is a BigInt type that use a memory
allocation to store its contents. The ownership would be transferred to the
return value

~~~
dorfsmay
But again, what would be the drawback(s) of using a pointer instead of
transfering the ownership?

Not rethorical, I'm genuinely curious as I used to struggle with the copy vs
move decisions, and a bunch of issues with ownership which went all away when
I started using pointer/borrowing everywhere.

~~~
Dylan16807
The downside to using a mutable reference instead of passing around ownership
is just that it would be awkward.

You'd have to write two extra lines of code to set up y and z separately from
calling add(), and you'd have to make them mutable which is an extra mental
burden.

And an immutable reference is bad because it would force extra objects to be
allocated, wasting time and cache space.

------
grenoire
Wow, elegant. This was probably conceived as an idea during the design phase
of the language, it seems _right_.

~~~
ratww
That 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)

------
lpghatguy
Sometimes 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)

------
saagarjha
There’s a number of fun C++ ones similar in spirit: std::move, for example.

~~~
jcelerier
The standard C++ way to free resources is the character '}'

~~~
wwright
Rust has the exact same semantics there. Drop is useful when you need to
explicitly notate that a value should end its life early.

~~~
klipt
void 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
No, it would not free it. It takes a reference to the pointer and does
nothing.

~~~
dkersten
unique_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
Yes, 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.

~~~
dkersten
Sorry, I edited right after hitting reply to clarify that I'm assuming the
intention was to std::move the unique_ptr in, since it was T&& and not T&.
Would that still not work?

~~~
Rusky
It would still not work. All std::move does is cast to T&&, enabling overload
resolution to pick a function with T&& as its parameter type.

~~~
dkersten
Thanks for explaining!

------
hinkley
Do 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
It 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-
le...](https://stackoverflow.com/questions/50251487/what-are-non-lexical-
lifetimes) for more.

~~~
Rusky
This is incorrect. Values still go out of scope and have their destructors run
at the same time.

NLL only affects values _without_ destructors.

~~~
oconnor663
I think `#[may_dangle]` is an exception to this, and the standard library puts
it on many (most?) container types.

~~~
Rusky
It 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)

------
newacctjhro
> 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!

------
cztomsik
For 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)

~~~
fao_
> 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.

While someone else is right that "zero-cost" refers to runtime cost rather
than compilation cost, dependencies are the biggest problem.

The program `spotifyd` takes over an hour to compile on my X200 laptop. This
is, for reference, the same amount of time that the Linux Kernel and GCC takes
to compile (Actually, I think GCC takes less time...). Most of the compilation
time is on the 300+ dependencies, that simply wrap C (and in some places,
replicate) libraries that I already have installed on my system!

~~~
stefan_
An X200 is also an ancient machine. Do you value your own time so little?
Clearly you are annoyed by the compile times?

You can get a $200 CPU that will blast through a kernel compile in 3 minutes.

~~~
fao_
> An X200 is also an ancient machine. Do you value your own time so little?
> Clearly you are annoyed by the compile times?

I value my security first and foremost. My X200 has zero binary blobs running
on the machine, it also has several other properties that I appreciate.

> You can get a $200 CPU that will blast through a kernel compile in 3
> minutes.

That assumes that I even have the money to afford that. Not all of us live in
silicon valley.

------
millstone

        let x = String::from("abc");
        std::mem::drop(&x);
        std::mem::drop(&x);
        std::mem::drop(&x);
        std::mem::drop(&x);

~~~
hathawsh
FWIW, 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
This 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&editio...](https://play.rust-
lang.org/?version=stable&mode=debug&edition=2018&gist=f2b4935fbb248718f1f2878ee4c53409)

~~~
hathawsh
Oh right, I forgot about the interaction with generics in this case.
Semantically, I suppose it's creating and dropping a reference 4 times.

------
flywithdolp
can 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?

~~~
likeliv
One 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
Keeping 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.

------
gautamcgoel
Gotta 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?

~~~
hu3
Interesting how developer views can differ.

Someone describes Go as "unacceptably crippled" while Uber engineering has
1500 microservices written in Go, making it their primary language.

[https://news.ycombinator.com/item?id=21226347](https://news.ycombinator.com/item?id=21226347)

~~~
mav3rick
These are orthogonal points. Linux Kernel is all in C and possibly drives the
world. Doesn't mean it's safe and amazing.

~~~
hu3
C was the only sensible choice back then.

Uber on the other hand has many languages available yet picked Go. So they
certainly don't think it is "unacceptably crippled".

~~~
jakear
There is an implicit "for my use-case" following "unacceptably crippled". So,
go is not "unacceptably crippled" for the use case of creating a ton of small,
simple, easily maintainable services at Uber, but it might be "unacceptably
crippled" for the use case of having fun with type systems, or whatever the
author was interested in at the time they were considering go as an option.

~~~
hu3
The paragraph starts with "The beauty of programming language design".

Even the most charitable interpretation would conclude it's speaking in broad
terms and not just "for my use case".

Full paragraph:

> 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.

------
jchw
> 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
It 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.

~~~
root_axis
In my anecdotal experience, the type of programmers that evangelize and talk
shit about programming languages tend to be on the less informed side of the
knowledge spectrum.

~~~
vertex-four
I occasionally program Go because it has an amazing amount of libraries - I
basically use it for random microservice type things that glue systems
together. (Most recently, an HTTP login endpoint that sends and receives XMPP
messages to authenticate the user.)

I don't like it. I program Rust the rest of the time. I'm considering learning
Perl5 so that at least my type system lets me do vaguely expressive things -
I'm utterly fed up of half my code looking like:

    
    
        foo, err := something()
        if err != nil {
            return nil, someErr{err}
        }
    

I got very used to Rust's try!() macro, now ? operator, and being able to map
over Results to convert between error types in a single line that feels much
less noisy - I have functions in Go that are a dozen or so lines that would've
been 3 in Rust, and tbh I struggle to follow what the function actually does
when 3 out of 4 lines are to do with the failure case.

I would consider "unacceptably crippled" to be a reasonable description of Go,
even if it's the best tool I have for some jobs.

~~~
root_axis
"unacceptably crippled" is a pejorative meant to insult, not a descriptive
criticism.

------
etxm
> unacceptably crippled like Go

I just spit mezcal on a stranger.

~~~
totalperspectiv
Were they okay with it once you explained what was so funny?

~~~
etxm
It was a friend of a friend. The mezcal coming out of my nose was humorous to
them.

