Deriving Traits in Rust with Procedural Macros
naftuli.wtf
naftuli.wtf
However, I think this could be done with a generic impl of WritableTemplate for all T where T: Template.
I found the linked article [1] much more helpful in explaining procedural macros, even though it didn't offer many reasons to use them (other than linking to Serde etc.).
[1] https://blog.rust-lang.org/2018/12/21/Procedural-Macros-in-R...
But, when the time came to implement my custom derive I had to consult many sources. I ended up piecing together what I needed from a combination of:
- Official documentation (The Book)
- Blog posts
- Reading Serde code (And Syn/Proc-Macro/Proc-Macro2)
I also found the introduction of the proc-macro2 shim crate (however well intentioned) caused quite a bit of confusion. Specifically, it wasn't clean if I should use proc-macro or proc-macro2, and if I should be using the TokenStream exported by the former or the latter. Or should I be using one in some cases, and the other in some cases. Ditto for a few other things that I just can't recall right now.I did get things put together eventually, but I don't feel I understand things well enough to explain to someone else...yet.
All that said, it's a hugely powerful feature and well worth the time if you need to do things that require intimate knowledge of the AST.
This could just be a slightly contrived example to show the neat kinds of things you can do with procedural macros, though. If you needed a reference to the original struct definition, for example, procedural macros allow you to do the kinds of transformations at compile time that other languages need runtime reflection for.
I've always stuck to what Fedora was shiping, and they seem to be shipping the stable versions. Yet I used procedural macros back in 2016 already [0]. Does my memory fail me, or is there some other change that happened now?
[0] https://github.com/rhn/gpx-rust/blob/master/gpx_debug/src/li...
That said, an equivalent library in Rust would be very useful.
Ownership and the borrows checker may have a steep learning curve, but are not very complex. The rules are quite simple, the learning curve is steep because most programmers do not typically think about ownership (though they should).
(In my experience in teaching Rust, things like trait impl coherency rules, object safety, and finding a good balance between static and dynamic polymorphism are much harder for students than understanding the ownership system.)
Borrowing - I still don't understand how or why a non-reference is implicitly consumed by passing it (read-only intention) by value(?) to another function and then can't be used again. Pony does explicit consumption.
There seems to be a need for training classes in Rust that explain the development philosophy, because it's not readily apparent from the online resources to anecdotal me who's able to code in Haskell, Elxir, Erlang, Clojure, C, C++17, Ruby, Python, Go, assembly and LLVM IR.
When I just tried to jump in, things didn't quite click.
By default the value is moved in Rust, just like with std::move in C++17, which you say you know. This is to avoid performance issues when you pass complex structures, such as vectors, around. If you want to copy your value, you have to call .copy() explicitly.
The way I think about this (which isn't quite how rust seems to) is that every value (including references) is always destroyed by passing it to a function, but gets implicitly copied if (arg is copyable && arg is referenced below). Eg:
T a = mkT() # create value
foo(&a) # create and immediately destroy/pass reference
bar(a) # => bar(copy(&a)) # create-and-pass copy
baz(a) # last use, so dont bother copying
For noncopyable values, this makes perfect sense; the callee got a value, so that value must have been moved out of the caller's variable. Treating copyable values the same way modulo the existence of a (T const ref -> T) copy function is just good consistency/orthogonality.I am not sure what you are trying to say. This:
bar(a) # => bar(copy(&a)) # create-and-pass copy
baz(a) # last use, so dont bother copying
is not possible in Rust if a's type is not a copy type. a will be moved when calling bar, so trying to pass it to baz will result in a compiler error.The story is quite simple: a value is always moved in a function call, unless it is a copy type (the type implements the Copy trait). When the type is a copy type, a bit-wise copy is made. References are not special: immutable references are copy types. Mutable references are not copy types (otherwise, they could be aliased).
You can find an overview of all copy types in the standard library in the implementations section of the Copy trait:
https://doc.rust-lang.org/beta/std/marker/trait.Copy.html#fo...
Yep, that's what I was trying to say. (Although I'd hadn't remembered that the Copy trait actually enforced bitwise-exact-copies-only, because why would you want (implicit) non-exact copies.)
It's not the surprise-copy-horror you'd expect, because there are no copy constructors, and nothing large is ever copied implicitly (you have to call `.clone()` or implement `Copy` trait for a type).
The move semantics can ensure there exists only one owning pointer to each object. It can be statically known who owns the object, and most importantly, who has to free it.
In Rust there's a move or a falling out of scope. Lifetimes are passive, and just describe the connection between references so that the compiler can check that they don't become dangling when values are destroyed in essentially the same places as C++ would destroy them.
> Borrowing - I still don't understand how or why a non-reference is implicitly consumed by passing it (read-only intention) by value(?) to another function and then can't be used again. Pony does explicit consumption.
Rust had a more pony-like model with annotations and different modes many years ago, but the model was unnecessarily complicated and was simplified to a "everything is pass-by-value" one:
- http://smallcultfollowing.com/babysteps/blog/2011/12/08/why-...
- http://smallcultfollowing.com/babysteps/blog/2012/10/01/move...
Things to know:
- Rust has no copy constructors (but does have a "this can be safely semantically duplicated by memcpy" marker trait (Copy), which cannot run arbitrary code. See https://stackoverflow.com/a/31013156/1256624 and https://stackoverflow.com/a/24253573/1256624.)
- A "move" is a memcpy (bitwise copy) of a value to a new location, where the source becomes inaccessible at compile time
- A "read-only" T is a separate type &T, which is a value in its own right (and &_ implements Copy, so can be passed-by-value multiple times without explicit copies).
- Every value of type T is always a fully-fledged T, with all of T's operations available
- Parameters of type T (e.g. fn f(x: T)) are thus full values
- For an arbitrary type T, there's no way to (implicitly) copy values of type T, so the only way to call a function with a T parameter is to have the callee take ownership/responsibility for the caller's T value (i.e. move it into the call)