More generally is there a list of Rust keywords? No searching for rust programming language keywords” seems to be helping.
Move converts any variables captured by reference or mutable reference to owned by value variables. Move is also valid before an asynchronous block.
This couldn't have been changed in backwards compatible way, however, the language pretty much needed a macro system, so it was stabilized anyway despite its issues. Ref annotates pattern bindings to make them borrow rather than move.
Maybe you want tighter interop between the Rust and Objective-C parts of your iOS app. Sections 3-5 might actually be useful to other people looking to write Rust macros, so even though this project is a toy, you may still get something out of reading the post.
Sheldon also has a blog post from near the start of the project that talks about his design process beyond the bare message-send implementation. My normal audience these days is probably Swift developers, but I expect this one to make rounds with at least some Rust people as well.
It’d be easy for both groups to not have much direct experience with Objective-C, the primary language used by Apple for both macOS and iOS since their releases in 2001 and 2007. And nearly everything you do with those objects is based on a dynamic dispatch model implemented in the runtime.
Because of the two parts of that sentence (“dynamic dispatch” and “implemented in the runtime”), people have come up with a lot of clever and powerful techniques to make programs simpler, more expressive, or more extensible, though sometimes at the cost of security, secrecy, and stability. So here’s the deal: nearly everything you do in Objective-C is calling methods by sending messages, and the way this works is that methods are regular C functions stored in a dispatch table keyed by an unique string called a selector.
It is likely that the most heavily-optimized piece of code in Apple’s libraries is objc_msgSend, which takes a receiver, a selector, and the arguments to the method, does a (cached) lookup of the selector in the receiver’s class’s dispatch table(s), and then jumps directly to the appropriate, polymorphically-selected implementation of the method. From this point on you’ll be expected to read Rust syntax without step-by-step explanations.
Given that the Objective-C runtime exposes a public API, we should be able to pretty much just call that from Rust, and indeed we can: Then it’s got a funny empty ex tern block to link the Foundation framework, but sure, that’s fine.
But then there’s a monstrosity of a line involving std::me::transmute, Rust ’s equivalent of Swift’s unsafeBitCast(_:) or C++’s reinterpret_cast (and now bit_cast) or C’s…well, okay, C doesn’t have a direct equivalent, but a plain old cast when you’re talking about function pointers like this. In C (and Swift, for that matter), that conversion would require explicitly specifying the type of all arguments and return values.
But Rust has really powerful type inference within a function body, and that extends to specifying only some generic parameters, and leaving others out. In this case I need to specify that we’re casting to a C function pointer with two arguments and a non-void return, but not anything more than that.
The remaining information will be filled in based on how the function pointer is used. But to take a whole block of code, and replace everything that looks like a message lock in that block…well, it might be possible with pattern-matching and quite a bit of recursion, but it’s going to be a lot easier to use procedural macros, a Rust macro interface written in Rust and used as a compiler plugin.
(Swift folks, basically what SwiftSyntax allows you to do, but invoked on-demand during compilation.) But it’s absolutely possible to make a find-and-replace macro like this; it just means a bit of recursion.
Of course, you could easily argue that calling Objective-C at all deserves an unsafe, especially since the compiler just believes the types you use. This is because objc_msgSend isn’t the only message dispatch method in Objective-C; there’s also objc_msgSend_stret, and two more that only show up in rare cases on certain platforms.
The built-in proc_macro and the proc-macro2 crate handle basic “flexing”, including matching parentheses, braces, and square brackets but not much else. Syn can take the TokenStreams that come from proc_macro and parse actual Rust syntax trees out of them; quote takes both tokens and syntax trees and puts them back into Downstream form.
And transform all code inside the function without a second level of nesting. My colleague (Cassie again) pointed out that this allows various tools built on Rust syntax to assume that anything outside a normal macro actually has valid syntax; only within a macro can Weird Stuff arise.
This is presumably because the compiler doesn’t even get to the point of invoking your macro before parsing the normal code. This wasn’t exactly a “gotcha” for me because I happened to spot it in David Today’s Procedural Macros Workshop repo README, even though I didn’t actually go through the exercises in that workshop.
Another important tip here is to enable the “extra-traits” feature for syn if you want to dump parsed syntax trees. This is the only way to have syn give you a ParseStream, which has all the useful methods for parsing Rust syntax.
I’m glad I remembered feature flags exist, since they don’t in the Swift ecosystem for dependencies, but before I added this to my Cargo.Tom config file expressions just immediately failed to parse. Unlike Swift packages, Rust crates can only have one library per crate, and procedural macros are already going to be different from normal libraries because they are built to run as part of compilation.
So the simplest thing to do is put the macros in a second crate nested in the main one: As mentioned, David Today made a workshop repo for learning about procedural macros.
I didn’t go through it myself but if you want a proper introduction with practice projects, this is probably a good place to start. For more serious Rust /Objective-C interop work, there’s the real obj crate, and Sheldon’s accompanying blog post, which is a lot more step-by-step than this one.
When I tweeted about this toy project, Ryan McGrath responded with his own fun experiment Cacao, which builds on obj to provide Rust wrappers around App Kit and UI Kit. Both Swift and Rust go beyond the set of types and operations supported by C, much more than Objective-C’s little “cast objc_msgSend to the type of the method you’re actually calling”, and don’t (necessarily) make information about that available at runtime.
Neither Swift nor Rust have a general “call a function by name on this type” operation; you have to go through a protocol/trait. Meanwhile, both languages support working with C interfaces, so that’ll be a bridge for some time yet.
Both languages offer indispensable features for modern software development: a sophisticated and integrated tool chain, memory safety, an open source development model, and strong communities of users. With that in mind, let’s look at the main ways Rust and Go differ, and the kinds of work each is best suited for.
On the list of Rust ’s major advantages, performance ranks right at the top with safety and ease, and may be the number-one item. What Rust does cost be an effort on the part of the developer to learn and master the language’s abstractions for memory management.
Memory management in Rust and Go are strongly related to the performance behaviors in both languages. This means the vast majority of memory management issues can be caught before a Rust program ever goes live.
Given how much of our world is built atop software that is routinely found to be unsafe due to bad memory management, the Rust approach is way overdue. Developers coming from the worlds of C, C++, C#, or Java must rethink how they write code when they sit down with Rust.
Programmers can write thousands of lines of Go code and never once have to think about allocating or releasing memory. Programmers can also perform some manual memory management in Go, but the language deliberately stacks the deck against doing so.
Compile times are short, and the Go runtime is faster than Python (and other interpreted, developer-friendly languages) by orders of magnitude. Rust ’s compile times also tend to be longer than equivalent Go programs, especially for applications with large dependency trees.
This remains true even after a concerted effort by the Rust project to shorten compile times. If a fast development cycle and the need to bring people on board a project quickly are top priorities, Go is the better choice.
These primitives make it easy to write applications (such as network services) that must handle many tasks concurrently without risking common issues like race conditions. Rust only recently gained native concurrency syntax, in the form of the asynchronous/.await keywords.
Before asynchronous /.await, concurrency came by way of a crate or package for Rust called futures. To that end, Rust and Go both interoperate with legacy C code, although with different restrictions in each case.
In other words, Rust can’t guarantee their memory or thread safety; you need to manually ensure that interfaces to C code are safe. In both Rust and Go, though, interoperability with C comes at some cost to the developer: more conceptual overhead, slower compile times, more complex tooling, harder debugging.