As someone who has been programming in Rust for nearly a year, even for commercial purposes, this article is baffling to me. I've found the compiler messages to be succinct and helpful. The package system is wonderful. It's dead easy to get something off the ground quickly. All it took was learning how and when to borrow.
I can see where the author comes from. I've been working with ^W^W fighting against Tokio this week, and the error messages are horrible. Representative example:
error[E0271]: type mismatch resolving `<futures::AndThen<futures::Select<futures::stream::ForEach<futures::stream::MapErr<std::boxed::Box<futures::Stream<Error=std::io::Error, Item=(tokio_uds::UnixStream, std::os::ext::net::SocketAddr)> + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result<(), ()>>, futures::MapErr<futures::Receiver<()>, [closure@src/server/mod.rs:74:18: 74:74]>>, std::result::Result<(), ((), futures::SelectNext<futures::stream::ForEach<futures::stream::MapErr<std::boxed::Box<futures::Stream<Error=std::io::Error, Item=(tokio_uds::UnixStream, std::os::ext::net::SocketAddr)> + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result<(), ()>>, futures::MapErr<futures::Receiver<()>, [closure@src/server/mod.rs:74:18: 74:74]>>)>, [closure@src/server/mod.rs:78:34: 83:6 cfg:_]> as futures::Future>::Error == ()`
--> src/server/mod.rs:85:15
|
85 | return Ok(Box::new(server));
| ^^^^^^^^^^^^^^^^ expected tuple, found ()
|
= note: expected type `((), futures::SelectNext<futures::stream::ForEach<futures::stream::MapErr<std::boxed::Box<futures::Stream<Error=std::io::Error, Item=(tokio_uds::UnixStream, std::os::ext::net::SocketAddr)> + std::marker::Send>, [closure@src/server/mod.rs:59:18: 59:74]>, [closure@src/server/mod.rs:60:19: 69:10 next_connection_id:_], std::result::Result<(), ()>>, futures::MapErr<futures::Receiver<()>, [closure@src/server/mod.rs:74:18: 74:74]>>)`
found type `()`
= note: required for the cast to the object type `futures::Future<Item=(), Error=()>`
I have hope that things will improve on this front when `impl Trait` lands.
EDIT: After re-reading this, I want to add that I don't mean to hate on Tokio. I like the basic design very much, and hope that they can work out the ergonomics issues and stabilize the API soon.
The grievances you and the article's author mention seem less to do with Rust itself, and more to do with this seemingly horrible futures library. As far as I can tell, it's still in the rust-lang-nursery, which is an indication it's not ready for prime time yet.
Yeah, i have recently finished a tokio based server. Working with future combinators is very frustrating. I accidentally captured a variable in a closure(should be cloned and moved), and it didn't tell me where it happened, just an error saying requires 'static lifetime for the variable.
The last tokio re-ergo really helped me and my classmate.. now we are getting stuff done, and we are enjoying it a lot, since we are combining our connections as instances of async state-machines (which, by the way, a given state may be a sub state-machine).
I recently wrote a few projects in Rust (C/C++/Go/JavaScript/Java/Python as background), and very much like the language. My 2 cents from my endeavors with Rust
I felt like all type errors are backwards. That is, "got" was the target you are giving your type to, not the type that you are passing. This may only happen in some cases, but I just started tuning the content of those errors out and instead adjusted randomly until things worked or the message changed.
I was often getting obscure type errors that were not at all related to the issue, and sometimes the compiler just insisted that just one more burrow would do, no matter how many burrows you stack on. This is definitely because I did stupid things, but the compiler messages were only making matters worse.
String vs. str is a pain in the arse. My code was littered with .as_str() and .to_string(). I never had the right one.
Enums are super nice, but it's very annoying that you cannot just use the value as a type. My project had a lot of enums (user-provided query trees), and it was causing a lot of friction.
There are also many trivial tasks where you think "Of course there is an established ecosystem of libraries and frameworks for this", and end up proven wrong. I mostly did find one library for the thing I needed, but often immature. The HTTP server + DB game seems especially poor.
In the end, I had to quit the fun and get work done (and others did not find playing with new tools as fun as I did), so I ported the project to Go and got productive. I took a fraction of the time to write in Go, the libraries are just so much more mature, it performs significantly better than the Rust implementation (probably because of better libraries—definitely not stating that Go is faster than Rust here), compile takes 1 (one) second rather than minnutes, and there is in general just much less friction.
On the flipside, it takes about 2-3 times as much Go than Rust to do the same task, even if it was way easier to write the Go code. The code is also a lot uglier. As an especially bad case, something that was a serde macro call in the Rust version is 150 lines of manually walking maps in the Go version.
> My code was littered with .as_str() and .to_string().
PSA: If you have a variable that's a String, you can easily pass it to anything that expects a &str just by taking a reference to it:
fn i_take_a_str(x: &str) {}
let i_am_a_string = "foo".to_string();
i_take_a_str(&i_am_a_string);
Every variable of type &str is just a reference to a string whose memory lives somewhere else. In the case of string literals, that memory is in the static data section of your binary. In this case, that memory is just in the original allocation of your String.
> Enums are super nice, but it's very annoying that you cannot just use the value as a type. My project had a lot of enums (user-provided query trees), and it was causing a lot of friction.
Note that OCaml has this feature of using-a-enum-value-as-a-type (or using a subset of enum values as a type, etc.). It works very well, but it quickly produces impossibly complicated error messages.
I'd like Rust to have this feature, eventually, but not before there is a good story for error messages.
Oh, and added thing that bugged me a lot: The error part of Result<T, E>. During my short time of coding Rust (I'll get back to it later), I never really found a way to ergonomically handle errors.
I find it really awkward that the error is a concrete type, making it so that you must convert all errors to "rethrow". Go's error interface, and even exception inheritance seems to have lower friction than this.
Yeah - I mean I'm only learning it as a kind of hobby at the moment but, working as a Java programmer, I find it incredible how helpful the compiler can be. It even gives you little helpful syntax hints and stuff.
Writing Rust code isn’t that hard, sure. The annoyences start when you try modifying code or moving things around.
Prototyping and editing code makes for most of my work, and Rust makes that a chore. That’s my main gripe with the language. It’s more like moving through molasses than encountering a brick wall.
It's interesting how perspectives differ; I love refactoring Rust code more than any language I've ever used, as it catches so many of my errors when doing so for me, at compile time.
> All it took was learning how and when to borrow.
That's disingenuous. It's really not as simple as you're trying to make it sound. But I don't understand the point of this guy's blog post, unless it's just to whine. There's so little real content in his post.
> As someone who has been programming in Rust for nearly a year, even for commercial purposes, this article is baffling to me.
The author introduces himself as having a Python background, and as someone who experienced some challenges wrapping his mind around SQL. I wouldn't expect anything different.
I write hardware drivers (mixed kernel/user-mode) for 100Gb/s FPGA network accelerators at work in C and C++ for 3 different OS's. I'd argue that I am very much the target audience for Rust.
And I also find that SQL can be quite infuriating to deal with, and had a high-friction experience with Rust.
Wait, what? Rust has what I consider the best documentation I've seen of any language. The docs explain things at a high-level, but concisely, and have numerous examples. The formatting is good, the keyboard navigation support is good, it's well-linked, and it has convenient features like links to the source and the ability to collapse everything but method headers for easier browsing. And it's extremely easy to add docs to your own project.
Maybe there are some dark corners filled with poorly documented unstable APIs that I haven't seen?
I wrote this piece hastily, and it really wasn't intended for this broad of an audience — I won't redact the existing wording I still think it's roughly right, but I do regret a lot of it.
Rust's docs are amazing in certain contexts — the book is great, the built-in support for documentation on types/functions/etc. is amazing, and compiling code examples are a very laudable idea.
What I'm speaking to here is more once you get into the broader ecosystem and start using a lot of non-core libraries. Oftentimes I found that the front page docs explaining the basic premise were concise and well-written, but that things got quite a bit harder when you started diving into individual classes and functions (put another way, as you started deviating from the happy path). This doesn't apply everywhere, but often the comments are very minimal and the documentation relies heavily on "types as documentation" in that there's a big list of all the traits and the functions on those traits that are implemented. In many, many cases there's little in the way of detail or examples.
I've written quite a bit of Rust now and have used many of the headliner projects. So far there have been very few crates where I didn't have to resort to eventually checking out the source tree and figuring out how to do some particular thing by examining its source code and test suite. I won't call out any single project in particular, but I found this to be the case in every one of `actix`, `clap`, `diesel`, `error-chain`, `horrorshow`, `hyper` and `juniper`, just to pick a few from the top of my `Cargo.toml` (it also happened with many other libraries). It's great that you can do this and open source is awesome, but ideally I could get by on just documentation, which is what you can do in many other languages.
Unfortunately, read with little context, it sounds like the tone of my piece was intended to crucify, but it's not. It can be simultaneously true that Rust's docs can still use lots of improvement and that the Rust team is doing an amazing job of improving them (there's just a lot of work and a long way to go). Both these facts are true with Rust.
I take documentation bugs seriously. If you did this with any of my crates, please file bugs. My guess is that other crate authors might feel the same, and that they would also appreciate bug reports.
Writing good docs is super hard, because in order to do it well, one must sink themselves entirely into the perspective of someone who is seeking answers. This is hard when you already have the answers.
> What I'm speaking to here is more once you get into the broader ecosystem and start using a lot of non-core libraries.
Don't even know how many times I've had to dig into the python sources to figure out why something wasn't working as intended...
My favorite: I was trying to get memory buffer working for an object (following the official docs) using the C-api and it just didn't work no matter how much I fiddled with it so I go digging through the python sources and find out the fully documented feature I was attempting to use wasn't even implemented. Well, half the PEP was implemented.
I'm probably just funny that way since if I can't figure something out from docs I just go read the sources.
Writing good docs is hard for sure! Especially for developers, you just want to write cool code :) but take into an account lifetime of the projects, compared to python all of them pretty young. For example I started actix-web just 5 months ago, sure it needs more documentation
I wonder idly if it would be useful to have 'cargo doc deps' that would generate docs for all your dependencies (or top level dependencies) in a project.
The rust docs are super useful, but not everyone bothers to publish them, and people tend to forget to pit full working examples in them.
Far far far better documentation. The Rust documentation is a mess and difficult to read for many people, but many involved in Rust seem to deny that it is a problem.
Docs team lead here. Specific feedback on improving the output of the docs is absolutely, 100% welcome. Without knowing what "it" is, I can't say if we're "denying that it is a problem."
We are constantly tweaking the layout of stuff, and have some larger plans on the way as well.
> The wall is impassable until a point that you make a key discovery which lets you make your through. Often that discovery is a eureka moment – it felt impossible only hours before.
These moments are my favourite, or at least most memorable, parts of learning. Which is what attracts me to hard, important concepts because they tend to be the most rewarding once you begin to grasp it.
I'll never forget when SICP's connection between programming and abstraction started to really click, after much effort, and the way it blew my mind, despite having programmed for a couple years. Really grasping the fundamental concept took the whole experience of programming to the next level - and made learning other things easier like a rolling snowball.
The fact the author's walls never got "smaller" is a real problem, that can be disheartening.
That said, brick walls aren't always bad things. Although it's often hard to tell whether it's a reflection of a poor design/structure of the thing you're learning, or just the learning material, or the learner themselves. Then there is the question of "necessary evils" of steep learning curves which, who knows, might be required entry-fee for a truly great language or tool (see: Emacs/Vim).
Haskell was much the same way for me. Each cliff I climbed had a new big cliff waiting. It got me into learning not just pure FP but also lambda calculus/set/category theory. It felt never ending. I ultimately never went "all in" with Haskell (maybe because of this) but what I did learn has been very useful in my non-Haskell programming elsewhere, so it's not all for naught. But it was admittedly a hard and significant time investment which isn't for everyone... nor necessary for being a productive programmer. But I don't regret it.
I am currently learning Rust and I feel this intensely[1].
I really want to like Rust, but I feel like the way they cope with no GC (lifetimes, borrowing) fights me at every turn, and really simple situations in other languages[2] become these intensely painful situations. Every time you think you've worked out how to fix a problem you find while you've fixed that one you've actually created 2 more.
Want to have a data structure of variable size (eg a struct with an Vector in it)? You can't do that, structs have to be fixed size. OK so I'll make it a reference to a Vector. That's great, but now you can't have a factory function because the lifetime goes out of scope. OK so I'll wrap my reference in a Box. OK that's great but now your OTHER reference: a trait (because traits are also of unknown size) is complaining. OK I'll wrap that in a Box as well. Sorry, you can't wrap this trait in a box because before your trait requires implementors to implement copy because at one point you have to use the trait in more than one place and references break the lifetimes and--- THROWS LAPTOP OUT WINDOW
I'm going to keep chipping away at it for a bit longer, but I can feel my interest waning.
[1] Except for the bad docs bit, I really like the docs, and the error messages are definitely earnest in their attempts to help you.
[2] that I'm familiar with, which are no languages that don't auto-GC
-- EDIT --
To everyone taking the first line of my intentional rant paragraph and pointing out it works fine… you're correct, I am mistaken! Let me paste the reply I made to the first person:
---
You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
It's important to note that in C++ you can easily run into problems with what you're describing: structs (objects) with different sizes being put into a vector. You may very well get the program to compile, but then run into odd bugs which come about because your objects get clipped to the size of the smallest possible (the base class). So any overridden methods which expect extra data in a subclass will not behave as expected.
So what you usually do here is have a pointer and a VTable and all that jazz. But there's been a resurgence in interest in putting data into contiguous blocks of memory. Check up on data driven design.
I don't think it's fair to compare GC'd languages to Rust's complexity. At least, compare C to Rust. But still, to be truly fair, weigh the usability differences against the safety differences. There are a lot of trade-offs here and perhaps this isn't the right way for you to go forward, but keep a broad view of the other aspects at play.
> It's important to note that in C++ you can easily run into problems with what you're describing: structs (objects) with different sizes being put into a vector.
This is simply wrong.
> You may very well get the program to compile, but then run into odd bugs which come about because your objects get clipped to the size of the smallest possible (the base class).
That has nothing to do with object sizes. You're describing the object slicing problem
> So what you usually do here is have a pointer and a VTable and all that jazz. But there's been a resurgence in interest in putting data into contiguous blocks of memory. Check up on data driven design.
> It's important to note that in C++ you can easily run into problems with what you're describing: structs (objects) with different sizes being put into a vector.
Baloney. In C++ vector<any> or vector<variant> accomplish this task without any problems whatsoever.
1. Automatic reference counting is a really good alternative to GC. It takes a little bit more book-keeping, but the performance characteristics are predictable since allocations/frees are handled along the way. Many GC implementations require execution to be halted while the reference graph is traced, which makes it a non-starter for applications trying to deliver predictable real-time performance.
2. Most of the limitations in Rust you're lamenting are there to ensure that your code is safe and performant. Rust requires adapting to very different design-patterns than you might be used to to get these benefits: if something is hard to do in Rust it's probably an anti-pattern with respect to memory performance or safety. Then again if performance isn't your main concern, maybe you don't need Rust.
The problem with reference counting is that it don't work with recursive structures and counters may overflow. Overflow or reaching some max value and returning runtime error are both problems.
Minor point: you can have Vectors in structs. Vector is a fixed-size type on the stack so it works fine. And other types in structs too. EDIT: sorry, I see you got to that before I'd finished this post :)
Rust is a language where you have to kind of take a step back before working with it to read about the design tradeoffs and why they were made. Certain things you're used to doing with other languages just won't work (e.g. doubly linked lists), and trying to coerce the language into being something it's not leads to you going on one of those frustrating circular rabbit hole journeys you described so nicely above, usually ending up with laptop defenestration.
I'm a week into Rust. I find like you the compiler quite helpful, the docs comprehensive but a bit impenetrable, and the ownership & lifetime system logical on its own terms but very, very unnatural to learn. I don't actually know if its worth it yet.
I am really, really enjoying the O'Reilly Rust book ("Programming Rust"). Maybe sign up for a 10 day Safari trial (free, no card) and give the first few chapters a read. It may help reset you.
> lifetime system logical on its own terms but very, very unnatural to learn
That is a succinct explanation of it, yes! Like I can read the documentation and nod along, and the rules seem simple. Actually then remapping your brain and how you want to achieve things seems incredibly challenging
> Want to have a data structure of variable size (eg a struct with an Vector in it)? You can't do that, structs have to be fixed size.
I'm unclear what you mean here, because Vecs in Rust do have a fixed size. You can see this by using std::mem::size_of on a Vec: for any type, a Vec is three words in size. You can see this documented in the stdlib documentation for Vecs: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees
"Vec is and always will be a (pointer, capacity, length) triplet. No more, no less."
So what you're asking for, a Vec in a struct, works just fine:
(Responding to myself to reply to the parent's edit)
> You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
Ah, it sounds like you're using traits as types directly, which is very much discouraged by Rust (especially in conjunction with taking references to those traits). What the language really prefers for you to do is to use traits as bounds on generic types to get rid of the dynamic dispatch and the consequent complications with lifetimes. The only time I'd suggest using traits in the manner you've described is when you need a heterogenous collection, which isn't common in my experience. You've just so happened to stumble across one of the patterns that I most suggest beginners not to do. :P
> In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
I hope that nobody here's making you feel stupid, that would be pretty silly. I've been helping people learn Rust for a long time and it's indeed common for people coming from GC'd/dynamic languages to feel like things are pretty alien (to some degree attributable simply to the differences inherent to systems programming). I as well came from Java/Python and found that there were enough people in the Rust community with that same background that there's no air of elitism suggesting that one ought to feel like a moron for e.g. not knowing what a pointer is. We're all here to help each other, eh? If you ever want to give learning Rust a try again and get stuck, feel free to come ask questions on #rust at irc.mozilla.org or reddit.com/r/rust.
> I really want to like Rust, but I feel like the way they cope with no GC (lifetimes, borrowing) fights me at every turn, and really simple situations in other languages[2] become these intensely painful situations.
Lifetimes/borrowing aren't for dealing with no GC, they are for dealing with a number of issues (e.g., shared-state parallelism) that GC doesn't help at all with.
> In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler.
In a sense, I think that's intentional with Rust. Not unnecessary difficulty, but Rust forces a lot of complexity that would otherwise be easy to overlook and cause runtime bugs to be dealt with upfront by the developer.
For the domain Rust aims at, that probably makes writing correct code easier on balance, but it does make lots of simpler cases harder and higher-overhead than they would be in Python, or even Java, or even in some cases C++, which also isn't GCed, but still leaves a lot of what Rust bakes into static compile-time checks as runtime footguns.
There's nothing at all preventing you from having a Vec in a struct, and the use cases for variable sized structs are pretty rare. It sounds more like you conflated having a different problem with having a Vec be in a struct and stuff like that poisoned your further attempts to understand the language
You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
> What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
You can use any type of pointer to refer to a trait object, not just Box. In particular, you can use &T where T is a trait. The vtable work happening at runtime is the same, but the object can live e.g. on the stack or in a vec somewhere. Of course, if you're using references, you have to convince the compiler that the object is going to stay put for as long as the reference exists, as usual for Rust. When that's not practical, usually a Box or Rc/Arc is the go-to solution. Could you tell me more about what makes Box not work for your use case?
Aside: Trait objects are one of the more complicated features of Rust, and they run into tricky limitations (like "object safety"). It's often people's first instinct to use trait object anywhere they would've used a shared base class in some other language, but that's not usually the best pattern. Using concrete types with trait bounds (`&T .. where T: MyTrait` rather than `&MyTrait`), or inventing a new enum to hold all the types you expect, or even just trying to make ordinary composition work, is usually both easier and more performant.
> You can use any type of pointer to refer to a trait object, not just Box. In particular, you can use &T where T is a trait.
You can, though due to the extra annotations required I don't suggest that people new to the language try to use trait objects with references. (Hell, I try to keep people new to the language away from trait objects entirely, they're pretty restrictive.)
My understanding is that technically the Vec object itself is fixed size under the hood (a pointer and size field), but as far as I can tell, this is what you're after. It needs no references or boxes.
> What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box
In what language can you do something like that? In C++, it would compile but anything that you put in the vector would get sliced down to the base type. In Java, everything you put in would get boxed.
To nit though, I think the right thing to do regarding the vector is the tie the vector's lifetime to the struct. (I don't know how exactly to do that syntactically)
>Even something as simple as abstracting a new helper function often turns into a veritable odyssey because getting type annotations right can be so difficult (especially where a third party library is involved).
This is a sentiment I can't say that I share or even understand. You have a compiler doing inference, it will tell you what the types are if you ask?
A lot of times when I find myself writing something where I'm unsure of the concrete type -- I'll just stick a bogus type ascription in the expression somewhere. Then I run `rustc` knowing full well that it will fail. Somewhere in the error will be a message of the form "found <x> expected <y>" and now I know what the inferred type is.
>The horribly anti-user futures system, compiler messages generated by a misused macros that are second only to C++ template errors in how egregiously difficult they are to parse, universally terrible documentation and lacking examples, unstable APIs, type annotation hell, and so much more.
Given the author's dig at the `futures` crate though, I have a feeling a lot of the verbosity in errors they are seeing is due to the extremely long chains of combinators that the futures crate encourages. I haven't run into these errors myself since I'm avoiding async IO in rust until an `await` style abstraction becomes available. In my opinion: jumping into using an async library/runtime that is undergoing heavy development is probably not the best place to start when it comes to learning Rust.
I think this is one thing that Go definitely got right: having concurrency baked into the language that encourages a "syncrhonous-style" of programming is absolutely the way to go for approachability. I'm not sure that I'd go so far as to call `futures` user-hostile, as the tokio devs are doing great work, but it certainly isn't user-friendly yet.
> A lot of times when I find myself writing something where I'm unsure of the concrete type -- I'll just stick a bogus type ascription in the expression somewhere. Then I run `rustc` knowing full well that it will fail. Somewhere in the error will be a message of the form "found <x> expected <y>" and now I know what the inferred type is.
I've found myself doing this a lot in order to find the type of an expression. I stick it in a variable, add an `variable.asdf();` somewhere and look for the error message "no method asdf() on type <x>".
Perhaps someone should suggest that to Rust, if nobody already has. (A quick Google didn't show anything, but I didn't try very hard.) Or even implement it; there's a decent chance that's about as easy a compiler feature as someone could start with.
This is a gap I would like to be filled by an IDE. Specifically, I'd like the inference that tells me the correct type in a compile error to auto-complete a function declaration for me. It would make the experience of pulling code into a helper function much easier.
They're completely different kind of docs that Python's. Python's are written by hand, with many examples and tips how to use the various tools. Rust's documentation very much feels generated, and the last time I checked methods were not grouped. So vast portions of a page are consumed by variants of methods (overloaded methods?) which work exactly the same except they take different argument type. Python documentation is text with methods and code samples inbetween. Rust's documentation is like someone iterated over functions and types and printed a message for each. There's A LOT of redundancy.
They problem isn't that they're auto-generated, but that they're poorly organized or displayed (not sure exactly; hard to put my finger on). Everything on godoc.org (e.g., https://godoc.org/github.com/weberc2/httpeasy) is autogenerated and the readability is top notch.
By contrast, Python (which is my day-job language) is a hot mess. Usually everything is on one page, and it's often unclear which class's `__str__` method documentation you're looking at. SQLAlchemy's docs are absolutely awful in this regard. Further, links between things are poor and inconsistent (precisely because they're not autogenerated), and the dynamic nature of the language means its up to the documentation author to be explicit about the expectations (this is less a problem for well-formed functions, but for things like Pandas where every function takes a dozen combinations of arguments, it's a nightmare).
I'm curious which docs you are talking about. The Python standard library has much less examples than Rust's in my experience. numpy has excellent documentation, but that's some library.
Also, I don't see the redundancy you are talking about. There is no overloading, except for traits, and for those the documentation is in one place and all the implementations are just listed, which seems pretty minimal to me.
Coming from well established js libs to python I have found the documentation really hard to deal with.
I'm not entirely sure why that is. I really like getting to a repo on github and having the docs in the readme. Both python and rust have their own language specific doc implementations ReadTheDocs and docs.rs (I think). And you usually have to go to a separate site to view them which is fine, but I really dislike ReadTheDocs. It anyways seems really hard to find what I actually want. Take flask and alembic
I did find the documentation for promises and tokio to be pretty confusing. But I had only been learning Rust for a few days at that point, so I think I was probably just not ready for it.
I went on to just use the basic sockets instead and that was pretty easy.
I have been working with Rust, full time, for a little less than 100 days. Like the author, I also came from Python (I authored Yosai).
Unlike the author, I haven't been hitting brick walls. I also have observed all of the warning signs that the futures bridge on the tokio highway is unfinished so I didn't cross the barrier and still try to use it anyway.
Instead, I have been working on myriad other synchronous parts with great success. I've made substantial progress in this time largely due to the Rust community and --- the Eco system! Sure, I've had to build custom parts but there haven't been showstoppers.
I'm from the Ruby community, did a year of Scala after leaving Ruby. I'm now with rust and after the first struggle, I don't really have trouble with Rust anymore. I know the design I need to do to get certain things done, I know what is allowed now easily with purely concurrent Rust, and when do you need to split things up into threads. Right now I can refactor a project from threads to reactor in a couple of days and deploy to production with Rust. And I know everything just works.
I'm interested to know what attracts Python programmers to Rust. It's a very different language so I'm surprised to see so many python programmers take it up. What are your reasons for working with it?
I didn't quit Python. That would be shooting myself in the foot. I just won't be using Python everywhere for everything. Programming in Python is such a pleasant experience because I can do so much with it with so little effort. Rust enables the same but in other ways. Further, I can bridge the two!
I'm obviously about as far from the modal Rust user as one can get, but at this point the language has completely melted away into the background. I'm often tempted to write smallish scripts in dynamic languages, but even for those I frequently choose Rust just for the Cargo ecosystem. In particular I never see a reason to use C++ unless I'm contributing to a codebase that's written in it.
It takes different programmers different amounts of time to get to this point, and I certainly believe we can do better to make it easy to get up to speed, especially when async I/O is involved. But it does come eventually.
Same here. I used to use Python for small one-off scripts, but I now primarily use Rust. I can write Rust nearly as fast as Python and Go now, plus there are a lot of useful crates for the type of command-line utilities that I write.
For me the benefit is that a Rust utility usually works when it compiles, plus I can easily deploy the binaries on other machines (without going through another pyenv/pip dance).
To give an example: I work a lot with dependency treebanks on CoNLL-X format. Over my past to years with Rust, I have accumulated a bunch of utilities for doing things like paritioning data, merging treebanks, shuffling treebanks, extracting forms/lemmas, checking for cycling graphs, etc. I use them nearly daily:
Same here. But then I have 5 years of experience writing Rust. Wow, that feels like a totally ridiculous thing to write, but it's true.
These days I work at a startup where we are writing everything in Rust and then write C binding and Python binding to Rust code in order to connect to the outside world.
EDIT: After re-reading this, I want to add that I don't mean to hate on Tokio. I like the basic design very much, and hope that they can work out the ergonomics issues and stabilize the API soon.
This is so awesome.
I felt like all type errors are backwards. That is, "got" was the target you are giving your type to, not the type that you are passing. This may only happen in some cases, but I just started tuning the content of those errors out and instead adjusted randomly until things worked or the message changed.
I was often getting obscure type errors that were not at all related to the issue, and sometimes the compiler just insisted that just one more burrow would do, no matter how many burrows you stack on. This is definitely because I did stupid things, but the compiler messages were only making matters worse.
String vs. str is a pain in the arse. My code was littered with .as_str() and .to_string(). I never had the right one.
Enums are super nice, but it's very annoying that you cannot just use the value as a type. My project had a lot of enums (user-provided query trees), and it was causing a lot of friction.
There are also many trivial tasks where you think "Of course there is an established ecosystem of libraries and frameworks for this", and end up proven wrong. I mostly did find one library for the thing I needed, but often immature. The HTTP server + DB game seems especially poor.
In the end, I had to quit the fun and get work done (and others did not find playing with new tools as fun as I did), so I ported the project to Go and got productive. I took a fraction of the time to write in Go, the libraries are just so much more mature, it performs significantly better than the Rust implementation (probably because of better libraries—definitely not stating that Go is faster than Rust here), compile takes 1 (one) second rather than minnutes, and there is in general just much less friction.
On the flipside, it takes about 2-3 times as much Go than Rust to do the same task, even if it was way easier to write the Go code. The code is also a lot uglier. As an especially bad case, something that was a serde macro call in the Rust version is 150 lines of manually walking maps in the Go version.
PSA: If you have a variable that's a String, you can easily pass it to anything that expects a &str just by taking a reference to it:
Every variable of type &str is just a reference to a string whose memory lives somewhere else. In the case of string literals, that memory is in the static data section of your binary. In this case, that memory is just in the original allocation of your String.Note that OCaml has this feature of using-a-enum-value-as-a-type (or using a subset of enum values as a type, etc.). It works very well, but it quickly produces impossibly complicated error messages.
I'd like Rust to have this feature, eventually, but not before there is a good story for error messages.
I find it really awkward that the error is a concrete type, making it so that you must convert all errors to "rethrow". Go's error interface, and even exception inheritance seems to have lower friction than this.
Dead Comment
Prototyping and editing code makes for most of my work, and Rust makes that a chore. That’s my main gripe with the language. It’s more like moving through molasses than encountering a brick wall.
Deleted Comment
That's disingenuous. It's really not as simple as you're trying to make it sound. But I don't understand the point of this guy's blog post, unless it's just to whine. There's so little real content in his post.
The author introduces himself as having a Python background, and as someone who experienced some challenges wrapping his mind around SQL. I wouldn't expect anything different.
And I also find that SQL can be quite infuriating to deal with, and had a high-friction experience with Rust.
Dead Comment
Wait, what? Rust has what I consider the best documentation I've seen of any language. The docs explain things at a high-level, but concisely, and have numerous examples. The formatting is good, the keyboard navigation support is good, it's well-linked, and it has convenient features like links to the source and the ability to collapse everything but method headers for easier browsing. And it's extremely easy to add docs to your own project.
Maybe there are some dark corners filled with poorly documented unstable APIs that I haven't seen?
I wrote this piece hastily, and it really wasn't intended for this broad of an audience — I won't redact the existing wording I still think it's roughly right, but I do regret a lot of it.
Rust's docs are amazing in certain contexts — the book is great, the built-in support for documentation on types/functions/etc. is amazing, and compiling code examples are a very laudable idea.
What I'm speaking to here is more once you get into the broader ecosystem and start using a lot of non-core libraries. Oftentimes I found that the front page docs explaining the basic premise were concise and well-written, but that things got quite a bit harder when you started diving into individual classes and functions (put another way, as you started deviating from the happy path). This doesn't apply everywhere, but often the comments are very minimal and the documentation relies heavily on "types as documentation" in that there's a big list of all the traits and the functions on those traits that are implemented. In many, many cases there's little in the way of detail or examples.
I've written quite a bit of Rust now and have used many of the headliner projects. So far there have been very few crates where I didn't have to resort to eventually checking out the source tree and figuring out how to do some particular thing by examining its source code and test suite. I won't call out any single project in particular, but I found this to be the case in every one of `actix`, `clap`, `diesel`, `error-chain`, `horrorshow`, `hyper` and `juniper`, just to pick a few from the top of my `Cargo.toml` (it also happened with many other libraries). It's great that you can do this and open source is awesome, but ideally I could get by on just documentation, which is what you can do in many other languages.
Unfortunately, read with little context, it sounds like the tone of my piece was intended to crucify, but it's not. It can be simultaneously true that Rust's docs can still use lots of improvement and that the Rust team is doing an amazing job of improving them (there's just a lot of work and a long way to go). Both these facts are true with Rust.
I take documentation bugs seriously. If you did this with any of my crates, please file bugs. My guess is that other crate authors might feel the same, and that they would also appreciate bug reports.
Writing good docs is super hard, because in order to do it well, one must sink themselves entirely into the perspective of someone who is seeking answers. This is hard when you already have the answers.
Don't even know how many times I've had to dig into the python sources to figure out why something wasn't working as intended...
My favorite: I was trying to get memory buffer working for an object (following the official docs) using the C-api and it just didn't work no matter how much I fiddled with it so I go digging through the python sources and find out the fully documented feature I was attempting to use wasn't even implemented. Well, half the PEP was implemented.
I'm probably just funny that way since if I can't figure something out from docs I just go read the sources.
The rust docs are super useful, but not everyone bothers to publish them, and people tend to forget to pit full working examples in them.
Far far far better documentation. The Rust documentation is a mess and difficult to read for many people, but many involved in Rust seem to deny that it is a problem.
We are constantly tweaking the layout of stuff, and have some larger plans on the way as well.
These moments are my favourite, or at least most memorable, parts of learning. Which is what attracts me to hard, important concepts because they tend to be the most rewarding once you begin to grasp it.
I'll never forget when SICP's connection between programming and abstraction started to really click, after much effort, and the way it blew my mind, despite having programmed for a couple years. Really grasping the fundamental concept took the whole experience of programming to the next level - and made learning other things easier like a rolling snowball.
The fact the author's walls never got "smaller" is a real problem, that can be disheartening.
That said, brick walls aren't always bad things. Although it's often hard to tell whether it's a reflection of a poor design/structure of the thing you're learning, or just the learning material, or the learner themselves. Then there is the question of "necessary evils" of steep learning curves which, who knows, might be required entry-fee for a truly great language or tool (see: Emacs/Vim).
Haskell was much the same way for me. Each cliff I climbed had a new big cliff waiting. It got me into learning not just pure FP but also lambda calculus/set/category theory. It felt never ending. I ultimately never went "all in" with Haskell (maybe because of this) but what I did learn has been very useful in my non-Haskell programming elsewhere, so it's not all for naught. But it was admittedly a hard and significant time investment which isn't for everyone... nor necessary for being a productive programmer. But I don't regret it.
I really want to like Rust, but I feel like the way they cope with no GC (lifetimes, borrowing) fights me at every turn, and really simple situations in other languages[2] become these intensely painful situations. Every time you think you've worked out how to fix a problem you find while you've fixed that one you've actually created 2 more.
Want to have a data structure of variable size (eg a struct with an Vector in it)? You can't do that, structs have to be fixed size. OK so I'll make it a reference to a Vector. That's great, but now you can't have a factory function because the lifetime goes out of scope. OK so I'll wrap my reference in a Box. OK that's great but now your OTHER reference: a trait (because traits are also of unknown size) is complaining. OK I'll wrap that in a Box as well. Sorry, you can't wrap this trait in a box because before your trait requires implementors to implement copy because at one point you have to use the trait in more than one place and references break the lifetimes and--- THROWS LAPTOP OUT WINDOW
I'm going to keep chipping away at it for a bit longer, but I can feel my interest waning.
[1] Except for the bad docs bit, I really like the docs, and the error messages are definitely earnest in their attempts to help you.
[2] that I'm familiar with, which are no languages that don't auto-GC
-- EDIT --
To everyone taking the first line of my intentional rant paragraph and pointing out it works fine… you're correct, I am mistaken! Let me paste the reply I made to the first person:
---
You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
So what you usually do here is have a pointer and a VTable and all that jazz. But there's been a resurgence in interest in putting data into contiguous blocks of memory. Check up on data driven design.
I don't think it's fair to compare GC'd languages to Rust's complexity. At least, compare C to Rust. But still, to be truly fair, weigh the usability differences against the safety differences. There are a lot of trade-offs here and perhaps this isn't the right way for you to go forward, but keep a broad view of the other aspects at play.
This is simply wrong.
> You may very well get the program to compile, but then run into odd bugs which come about because your objects get clipped to the size of the smallest possible (the base class).
That has nothing to do with object sizes. You're describing the object slicing problem
https://en.wikipedia.org/wiki/Object_slicing
The cause of this problem is ignorance and incompetence regarding fundamental aspects of the language.
in particular, boost.polycollection is a nice implementation of heterogeneous vectors: http://www.boost.org/doc/libs/develop/doc/html/poly_collecti...
Baloney. In C++ vector<any> or vector<variant> accomplish this task without any problems whatsoever.
Dead Comment
1. Automatic reference counting is a really good alternative to GC. It takes a little bit more book-keeping, but the performance characteristics are predictable since allocations/frees are handled along the way. Many GC implementations require execution to be halted while the reference graph is traced, which makes it a non-starter for applications trying to deliver predictable real-time performance.
2. Most of the limitations in Rust you're lamenting are there to ensure that your code is safe and performant. Rust requires adapting to very different design-patterns than you might be used to to get these benefits: if something is hard to do in Rust it's probably an anti-pattern with respect to memory performance or safety. Then again if performance isn't your main concern, maybe you don't need Rust.
The problem with reference counting is that it don't work with recursive structures and counters may overflow. Overflow or reaching some max value and returning runtime error are both problems.
1) GC is lazy, whereas RC is eager;
2) the use of "pure" RC may lead to memory leaks.
Dead Comment
Rust is a language where you have to kind of take a step back before working with it to read about the design tradeoffs and why they were made. Certain things you're used to doing with other languages just won't work (e.g. doubly linked lists), and trying to coerce the language into being something it's not leads to you going on one of those frustrating circular rabbit hole journeys you described so nicely above, usually ending up with laptop defenestration.
I'm a week into Rust. I find like you the compiler quite helpful, the docs comprehensive but a bit impenetrable, and the ownership & lifetime system logical on its own terms but very, very unnatural to learn. I don't actually know if its worth it yet.
I am really, really enjoying the O'Reilly Rust book ("Programming Rust"). Maybe sign up for a 10 day Safari trial (free, no card) and give the first few chapters a read. It may help reset you.
That is a succinct explanation of it, yes! Like I can read the documentation and nod along, and the rules seem simple. Actually then remapping your brain and how you want to achieve things seems incredibly challenging
I'm unclear what you mean here, because Vecs in Rust do have a fixed size. You can see this by using std::mem::size_of on a Vec: for any type, a Vec is three words in size. You can see this documented in the stdlib documentation for Vecs: https://doc.rust-lang.org/std/vec/struct.Vec.html#guarantees
"Vec is and always will be a (pointer, capacity, length) triplet. No more, no less."
So what you're asking for, a Vec in a struct, works just fine:
Can you elaborate on what trouble you're having?> You're absolutely right. What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
Ah, it sounds like you're using traits as types directly, which is very much discouraged by Rust (especially in conjunction with taking references to those traits). What the language really prefers for you to do is to use traits as bounds on generic types to get rid of the dynamic dispatch and the consequent complications with lifetimes. The only time I'd suggest using traits in the manner you've described is when you need a heterogenous collection, which isn't common in my experience. You've just so happened to stumble across one of the patterns that I most suggest beginners not to do. :P
> In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
I hope that nobody here's making you feel stupid, that would be pretty silly. I've been helping people learn Rust for a long time and it's indeed common for people coming from GC'd/dynamic languages to feel like things are pretty alien (to some degree attributable simply to the differences inherent to systems programming). I as well came from Java/Python and found that there were enough people in the Rust community with that same background that there's no air of elitism suggesting that one ought to feel like a moron for e.g. not knowing what a pointer is. We're all here to help each other, eh? If you ever want to give learning Rust a try again and get stuck, feel free to come ask questions on #rust at irc.mozilla.org or reddit.com/r/rust.
Lifetimes/borrowing aren't for dealing with no GC, they are for dealing with a number of issues (e.g., shared-state parallelism) that GC doesn't help at all with.
> In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler.
In a sense, I think that's intentional with Rust. Not unnecessary difficulty, but Rust forces a lot of complexity that would otherwise be easy to overlook and cause runtime bugs to be dealt with upfront by the developer.
For the domain Rust aims at, that probably makes writing correct code easier on balance, but it does make lots of simpler cases harder and higher-overhead than they would be in Python, or even Java, or even in some cases C++, which also isn't GCed, but still leaves a lot of what Rust bakes into static compile-time checks as runtime footguns.
In any case, my point remains the same: it is very challenging-- at least for someone used to just creating data structures and letting GC handle it-- to build code that does what you want, and you spend large amounts of time "fighting" the compiler. I am OK if people wish to peg that on me being stupid or whatever, it doesn't change the core point: it's hard for new people to get into, and if you're wanting there to be less Electron apps and more native apps things like Rust being easy to use seems important for that.
> What I really meant was that you can't have Vec<T> where T is a trait without also wrapping that in a box, which for me in turn doesn't work for a bunch of other reasons.
You can use any type of pointer to refer to a trait object, not just Box. In particular, you can use &T where T is a trait. The vtable work happening at runtime is the same, but the object can live e.g. on the stack or in a vec somewhere. Of course, if you're using references, you have to convince the compiler that the object is going to stay put for as long as the reference exists, as usual for Rust. When that's not practical, usually a Box or Rc/Arc is the go-to solution. Could you tell me more about what makes Box not work for your use case?
Aside: Trait objects are one of the more complicated features of Rust, and they run into tricky limitations (like "object safety"). It's often people's first instinct to use trait object anywhere they would've used a shared base class in some other language, but that's not usually the best pattern. Using concrete types with trait bounds (`&T .. where T: MyTrait` rather than `&MyTrait`), or inventing a new enum to hold all the types you expect, or even just trying to make ordinary composition work, is usually both easier and more performant.
You can, though due to the extra annotations required I don't suggest that people new to the language try to use trait objects with references. (Hell, I try to keep people new to the language away from trait objects entirely, they're pretty restrictive.)
My understanding is that technically the Vec object itself is fixed size under the hood (a pointer and size field), but as far as I can tell, this is what you're after. It needs no references or boxes.
https://play.rust-lang.org/?gist=8d88b83daa2f389892bfa95c8db...
Deleted Comment
In what language can you do something like that? In C++, it would compile but anything that you put in the vector would get sliced down to the base type. In Java, everything you put in would get boxed.
To nit though, I think the right thing to do regarding the vector is the tie the vector's lifetime to the struct. (I don't know how exactly to do that syntactically)
This is a sentiment I can't say that I share or even understand. You have a compiler doing inference, it will tell you what the types are if you ask?
A lot of times when I find myself writing something where I'm unsure of the concrete type -- I'll just stick a bogus type ascription in the expression somewhere. Then I run `rustc` knowing full well that it will fail. Somewhere in the error will be a message of the form "found <x> expected <y>" and now I know what the inferred type is.
>The horribly anti-user futures system, compiler messages generated by a misused macros that are second only to C++ template errors in how egregiously difficult they are to parse, universally terrible documentation and lacking examples, unstable APIs, type annotation hell, and so much more.
Given the author's dig at the `futures` crate though, I have a feeling a lot of the verbosity in errors they are seeing is due to the extremely long chains of combinators that the futures crate encourages. I haven't run into these errors myself since I'm avoiding async IO in rust until an `await` style abstraction becomes available. In my opinion: jumping into using an async library/runtime that is undergoing heavy development is probably not the best place to start when it comes to learning Rust.
I think this is one thing that Go definitely got right: having concurrency baked into the language that encourages a "syncrhonous-style" of programming is absolutely the way to go for approachability. I'm not sure that I'd go so far as to call `futures` user-hostile, as the tokio devs are doing great work, but it certainly isn't user-friendly yet.
I've found myself doing this a lot in order to find the type of an expression. I stick it in a variable, add an `variable.asdf();` somewhere and look for the error message "no method asdf() on type <x>".
Perhaps someone should suggest that to Rust, if nobody already has. (A quick Google didn't show anything, but I didn't try very hard.) Or even implement it; there's a decent chance that's about as easy a compiler feature as someone could start with.
> universally terrible documentation and lacking examples, unstable APIs, type annotation hell, and so much more.
The docs in Rust are by far some of the best I've seen, without specifics it's really hard to understand what issues he hit.
https://doc.rust-lang.org/std/vec/struct.Vec.html
FWIW I'm not a huge fan of Python's docs because I can't quickly scan them to track down that one detail about a function I was using.
Rust's docs also let you collapse everything which makes browsing them much faster.
By contrast, Python (which is my day-job language) is a hot mess. Usually everything is on one page, and it's often unclear which class's `__str__` method documentation you're looking at. SQLAlchemy's docs are absolutely awful in this regard. Further, links between things are poor and inconsistent (precisely because they're not autogenerated), and the dynamic nature of the language means its up to the documentation author to be explicit about the expectations (this is less a problem for well-formed functions, but for things like Pandas where every function takes a dozen combinations of arguments, it's a nightmare).
Also, I don't see the redundancy you are talking about. There is no overloading, except for traits, and for those the documentation is in one place and all the implementations are just listed, which seems pretty minimal to me.
I'm not entirely sure why that is. I really like getting to a repo on github and having the docs in the readme. Both python and rust have their own language specific doc implementations ReadTheDocs and docs.rs (I think). And you usually have to go to a separate site to view them which is fine, but I really dislike ReadTheDocs. It anyways seems really hard to find what I actually want. Take flask and alembic
So you'd suggest adding a manner of grouping methods in rustdoc?
I went on to just use the basic sockets instead and that was pretty easy.
I've been learning Tokio just this week, and I think this has to do with their ongoing refactoring away from the tokio-core crate to the tokio crate.
Unlike the author, I haven't been hitting brick walls. I also have observed all of the warning signs that the futures bridge on the tokio highway is unfinished so I didn't cross the barrier and still try to use it anyway.
Instead, I have been working on myriad other synchronous parts with great success. I've made substantial progress in this time largely due to the Rust community and --- the Eco system! Sure, I've had to build custom parts but there haven't been showstoppers.
It takes different programmers different amounts of time to get to this point, and I certainly believe we can do better to make it easy to get up to speed, especially when async I/O is involved. But it does come eventually.
For me the benefit is that a Rust utility usually works when it compiles, plus I can easily deploy the binaries on other machines (without going through another pyenv/pip dance).
To give an example: I work a lot with dependency treebanks on CoNLL-X format. Over my past to years with Rust, I have accumulated a bunch of utilities for doing things like paritioning data, merging treebanks, shuffling treebanks, extracting forms/lemmas, checking for cycling graphs, etc. I use them nearly daily:
https://github.com/danieldk/conllx-utils/tree/master/src/bin
These days I work at a startup where we are writing everything in Rust and then write C binding and Python binding to Rust code in order to connect to the outside world.