There is an advocate on our team that wants to migrate our web service from Nodejs to Rust.
While I am not a huge fan of Typescript, at least libraries are readily available and generally easy to use. On the other hand, being able to show that we are able to do monitoring, user auditing, ORM, opentracing, gRPC-web with Rust is non trivial.
Now that I think of it, being able to do a "hello world" on any language is pretty simple. But having all the tools around it to build a production level service is a different story.
Rewriting is very expensive, and the advocate will need to make a VERY strong case for Rust if they're asking your company to invest in replacing it - and alternatives have to be suggested as well, including other languages and a good list of things wrong with Node / TS. Consider developer availability as well.
I don't think a strong enough argument can be made. I'm sure you CAN write web services in Rust, and that in very specific cases it'll have some benefits over Node, but honestly very few people work in an area like that.
Disclaimer: I've settled on using Go to rewrite an existing application. The original app was written in PHP; nothing wrong with PHP per se, but the existing codebase is a mess and the PHP version is hard to keep up to date because of LTS versions of operating systems + very slow and careful updates at our customers (it's network infrastructure). For me, switching to a compiled language that produces a self-contained executable was a compelling argument. I have to admit that I do kinda pine for something like Java again though.
Swift / Vapor is amazing as well. They just released version 4, which streamlined and tidied up lots of things, can't recommend it enough. There's just something solid about Swift's strictness and compile time checks, that make it easy to be sure you're handling all possible code paths, and you can be reasonable confident it works and won't break all the time. Also very lean on dependencies, mostly unopinionated, and performance/mem-usage is top tier too. Only con is probably, you're bound to Xcode (and therefore macOS) for development, I guess you could try to set it up in VSCode, but haven't heard of it and experience will probably be not so good.
In this case Swift is being used on the server, so that could be an officially supported platform[0] which if I recall correctly is macOS, Ubuntu, CentOS, Amazon Linux 2, and Windows as of Swift 5.3.
I get that, but it still doesn't explain why the above poster used the MacOS market share as a detractor in this case. If you value languages that don't dictate hardware/software for you, then Apple was probably never a serious contender for you.
Yeah. I am being very cautious about their desires. I learned that the best way to discourage someone from taking a large project is to show how much work it would be. Just shutting down developers is not nice.
I agree too. Rewrites are a scary thing. The best is to do some test with a few new micro services and see how it feel to code from 0 to production. Learning the tool chain etc..
That over-engineering is what save us from clunky solutions that are good for hello world applications and HN like posts, but fail when doing deployments across heterogeneous platforms on Fortune 500 IT departments with endless number of external contractors and technology stacks hardly seen elsewhere.
And if I have to pick between managing WebSphere containers and taking care of k8s, I will rather pick WebSphere.
The learning curve should be beaten after a few weeks and is definitely lower than learning rust. On the other hand, the productivity and expressiveness return on investment is huge.
Spring is arguably the state of the art framework on the server and is integrated with so many powerful technologies.
> On the other hand, the productivity and expressiveness return on investment is huge. Spring is arguably the state of the art framework on the server and is integrated with so many powerful technologies.
Having worked with Spring for years (and still working with it, unfortunately), I have found exactly the opposite.
It does way too much dynamically, making debugging hard and rendering the type-system almost useless.
Sure, it comes with a lot of libraries for handling a large varieties of tasks, but they all seem to be half-assed, and much of the time I either have to work around their limitations or write something myself anyway.
I really don't see why the same thing couldn't have been achieved by writing a bunch of useful libraries that don't depend on a dependency injection framework (which I also don't find much value in).
Finally, it takes forever to startup, making testing a pain. It even means that JUnit tests are slow.
It's like any framework: once you learn it's idioms, you can be insanely productive. Unlike other frameworks, enterprise Java is all about flexibility, so in Spring land, it's often in incredible easy to tune or replace lower level components like connection pools, etc.
As such, I'd describe it as a good combination of highly dynamic architecture with lots of manual control. Of course, all of this is enabled with copious amounts of magic, which is usually why people don't like Spring.
Rust is much too low level for most glue/web services. Unless you have a specific high performance requirement (and Go doesn't meet this), there's no real strong case for migration here.
Rust itself is not inherently "low level" per se. But others are probably right that the whole web services ecosystem for Rust is rather half-baked at this time, the OP notwithstanding.
But I don’t? The borrow checker takes care of that? String vs &str is trivial to get ones head around, usually it’s really easy to decide whether you’d like to pass a reference or ownership, and worst comes to worst, sprinkling some copy/clone etc to get things sorted quickly still yields a binary that’s faster and more robust than something I can whip up in Python...
The borrow checker only checks, it does not solve the problem. In other languages the problem does not even exist to begin with.
It is not a trivial problem to solve (as you claim), otherwise we would have never needed the borrow checker to avoid memory bugs, nor higher level languages to speed up development by avoiding the problem altogether.
If you are going to end up sprinkling clones, heap allocating and reference counting, then you could have used C#, Java or JS to begin with which are plenty fast with their JIT/VMs and not think about memory at all.
Finally, comparing against Python is a very, very low bar for performance.
> In other languages the problem does not even exist to begin with
I am going to disagree here, because I've run into my share of memory issues in Python and C#/F#, and I'm sure by this point, everyone is well acquainted with Java's memory issues.
> It is not a trivial problem to solve (as you claim), otherwise we would have never needed the borrow checker to avoid memory bugs, nor higher level languages to speed up development by avoiding the problem altogether.
I'm not claiming that memory management is a trivial problem, I'm saying the borrow checker takes care of enough and the compiler/clippy hints when I do something wrong help me fix it easily enough. I write code slightly slower than I would in Python, but at the end, what I get from the Rust code is something that is more robust and more hardware efficient.
> If you are going to end up sprinkling clones, heap allocating and reference counting, then you could have used C#, Java or JS to begin with which are plenty fast with their JIT/VMs and not think about memory at all.
Rusts type system is enough to make me want to use it over dotnet, JS is a language with...some issues...that is fortunate enough to have a nice JIT, I consider it a serious choice for doing anything except web front-ends. I find C# needlessly convoluted and I dislike all the implicit mutability, but those complaints are very subjective.
The difference is that even if I have some clones and ref counts, they're rare, and the resulting binary is still outrageously fast, and has clear indicators of where to come back to and improve so as to not need the clone/reference counting/etc.
> Finally, comparing against Python is a very, very low bar for performance.
I compare against Python because that's the other language I do most of my work in.
You were talking about the borrow checker, which is mainly about memory safety, not memory limit issues.
In Python, C#, Java, JS... you are memory safe without dealing with memory management nor a borrow checker.
There are many languages running on top of those VMs for all kinds of tastes (OOP, functional, strict type systems, loose ones...). Claiming Rust leads to more robust software than any of those is an exceptional claim, but even if that were true, the key is the development cost.
A typed language is a typed language, there are other languages that are easy to get performance out of. I’m not a rust dev and I’m highly skeptical it will be used outside of firefox and a few niche projects after this initial hype train dies off. What other features would make me pick rust over golang or one of the interpreted languages?
> a typed language is a typed language
Well yeah, but not every type system is equal. For example I vastly prefer Rust's type system to C's because of Options instead of null and enums as sum types.
> what other features would make me pick rust over golang
Generics, iterators, pattern matching, etc. There's lots of features Rust has that golang doesn't; that's not necessarily a good thing but for what I do it is. IMO the only good thing about golang's featurelessness is the compile times and the standard library.
As for interpreted languages, IMO it's just better to be able to catch errors at compile time.
> Well yeah, but not every type system is equal. For example I vastly prefer Rust's type system to C's because of Options instead of null and enums as sum types.
Fair enough. But I'm not advocating using C here either.
> As for interpreted languages, IMO it's just better to be able to catch errors at compile time.
Just because you have a garbage collector doesn't mean you don't have to worry about memory management. I've see too many problems pop up because people don't understand how memory is managed in their GC'd language.
This isn’t true, in all languages with one you can ignore the garbage collector and still get work done. It may not be the most efficient but you still get work done. Let’s get a fresh out of code bootcamp grad in here and throw two languages in front of them if you want to test this.
You may be able to get work done, but I've seen actual bugs because people didn't understand how memory was managed. For example, not realizing that passing an object to a function was passing a reference, and not a copy. These are things that are explicit in Rust.
This won't result in any security related bug, you'd be updating the referenced version instead of a copied version. Both testing and use of the written code will show this "bug" if it's in fact a bug for this specific codebase. So now the question is, does rusts difficult learning curve warrant removing this "maybe" bug? There are other things to consider as well, memory fragmentation, performance etc. Have you measured the performance of code that both copies and updates?
But this is still using a hammer to screw in a nail. Rust is a systems language, it’s a junior dev move to force it into a web server. Use go or typescript for this, not rust. Just like I would write c++ for a backend unless i’m trying to shave off some nanoseconds.
Actually the OP only wrote that the current state of the ecosystem is surprisingly mature, but he doesn't recommend writing anything serious in it yet.
Personally I don't see the point to implement a typical web application in Rust - the performance improvements you get will be lost on IO-bound applications, but you'll still be saddled with the complexity of the memory management. I'd rather suggest to rewrite VS Code or the Slack client in Rust (i.e. apps which currently use Web technologies on the desktop) - those would definitely benefit more from increased performance and reduced memory footprint...
> the performance improvements you get will be lost on IO-bound applications
Performance starts mattering even in IO-bound applications as soon as you're trying to seriously scale out. Especially when running on a cloud-based platform. As for "the complexity of memory management", people like to bring this up about Rust but OP suggests that it's not a huge concern with the language.
I do agree that rewriting stuff like Electron-based apps should be a priority, and that Rust can help this via easy bindings to native OS and GUI platforms.
Generally when I’ve successfully advocated for new languages or tools at work, it’s been by gradually introducing the language in newer and fairly self-contained projects where the risk is relatively low. This provides a safe space to explore all the concerns you just mentioned without compromising any working code.
If you have some interest in Rust, that’s what I’d personally recommend instead of migrating an existing service to a different language.
I'd say there is value in learning for the sake of learning.
Even if you later on decide that the Rust implementation is not production-ready, you can draw conclusions from the project, and the next time someone considers Rust for a bigger project, there is a member on the team who can provide insight into potential issues. Of course, the project would have to be sufficiently small to not waste too much time.
Your development cost will skyrocket.
Even if you ignore everything that the ecosystem may not offer to you at this point (which is a big deal), you will have a hard time to find experienced Rust developers that like to build web services with it.
I come from a C# background, but to list out a few hurdles that I have already deal with with TypeScript/NodeJs.
1. C#'s AsyncLocal has made a few things simpler to trace SQL queries to a request.
2. We chose to use hapi.js some 2 years ago, because we found the interface superior to Express, but I did not expect the sole developer of hapi.js to decide stop working on the project [1], and left my head scratching on what I am going to do about that.
3. TypeScript's interface sometimes feels too much like just "suggestions", and every once in a while run across scenarios that are not fully supported. One that I really dislike is that Sequelize's "where" options has no type support for the model. [2]
4. Sometimes libraries does not use async correctly, and breaks stack traces.
5. Sometimes TypeScript's generic errors can be as bad as C++.
My favorite feature of TypeScript is being able to just define types/objects in-line, but I actually like to stay on the side of caution and stability on a large project with many people.
I can feel the pain on all 5 points. One thing I did notice though is that NodeJS has such a huge breadth of packages that its _very_ hard to actually pick something good. But there is almost always an alternative that's maybe not as popular, but is a lot more "solid" alternative.
For example we used SOHU-Co/kafka-node for a while as a kafka client, until we hit some bugs that made us dig through its internals and we realised it had some deep issues. We then switched to kafkajs which turned out to be much more mature and polished, even though it was less "popular".
Sequalize in particular I think was developed in an era before TypeScript was a thing so it follows the ideals of that time, more in line with Ruby and being easy to use and malleable. We switched to using slonik for our query needs, with a more declarative and static approach, skipping ORMs and query builders altogether - just raw strictly typed queries. I think in the end its a better approach for our needs.
I guess what I'm trying to say is that TypeScript was built to be able to handle _all_ of the weird and wonderful world of JS from its most amateurish and fun, to its most solemn and strict. And it's just a matter of picking up where on the spectrum you want to operate and make your dependencies match that vibe. It's limiting and freeing at the same time.
I feel like I have been to hell and back with ORMs, between Sequelize, Entity Framework, Hibernate, SQLAlchemy, etc.. and frankly, I think they just cause more headaches than solve problems.
I would love to have strongly typed SQL queries, but I have found that Dapper [1] fills a special place in my heart.
I've been following https://github.com/adelsz/pgtyped for awhile. It should give you TS types from sql files (and even sql template literals) directly. Though I haven't used it in prod. Might be worth a look.
Not the OP but my take is that Typescript has done great things for improving JS but it's still JS and carries a lot of it's baggage with it. There is only so far you can take it while maintaining compatibility and the type system is overly complicated in places because of this. It also doesn't really give you the same guarantees that something like Rust does, I don't believe the two type systems are that comparable. On top of that unless you use all TS packages types are maintained and installed separately adding to an already tricky to maintain dependency graph that comes with Node development.
As far as I know, you need grpc-web only because there is no direct grpc implementation for javascript. For rust, c++ etc. you would use grpc natively.
grpc-web is attractive to our use case because it would take away from manually implementing REST interfaces and every once in a while having type mismatches. The less room for mistakes, the better.
.NET Core just recently last month that gRPC-web is stable [1]. It would remove a huge amount of boilerplate in setting up services on both client and server side.
Some people also recommend to use Envoy [2], but usually the less cogs the better.
I would wait and see whether GraalVM would not help significantly with performance. After that I would try running the web service with Deno. Then I would rewrite in Go, then I would rewrite in Rust. That is not to say I think Go is better than Rust in every dimension (I prefer Rust for most things), but I think the Go community have really optimised for this one single usecase.
While I am not a huge fan of Typescript, at least libraries are readily available and generally easy to use. On the other hand, being able to show that we are able to do monitoring, user auditing, ORM, opentracing, gRPC-web with Rust is non trivial.
Now that I think of it, being able to do a "hello world" on any language is pretty simple. But having all the tools around it to build a production level service is a different story.