Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: how we pick programming languages in W3S #27

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

hannahhoward
Copy link
Member

@hannahhoward hannahhoward commented May 10, 2024

Preview

This is a proposed process for thinking about what programming languages we use in W3S.

Of note: there are no immediate proposed changes to the list of programming languages we use in W3S. Rather this is a framework for ongoing discussions and future language changes.

If you would like to propose a new language in the future, the process for doing so is described herein.

- Go has an extremely powerful concurrency model with green threads
- These two components together make Go one of the most powerful languages for writing servers of any kind. The challenge of server programming is not super fast compute, but super high concurrency execution. Go excels at this, which matters cause our project has a lot of servers involved.
- Go's ability to do binary distributions by default is a major plus for kind of project.
- By and large, people get stuff done quickly in Go because Go is extremely simple. It's very fast to learn, easy to read, and it all looks the same. The only problem is its extremely boring. (Note from Hannah: When I first started writing Go I hated it. I thought someone took the joy out of programming. Now I love it cause I get my joy from what I'm building, not my programming language)
Copy link
Contributor

@gammazero gammazero May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only problem is its extremely boring

I have found Go to be one of the most interesting languages from my first use, and up to the present. It is interesting because it lets you do everything you want efficiently and elegantly, and still stays out of your way. It provides an excellent balance of what is most needed and useful but nothing more to complicate things. I have not yet encountered another language that gets the balance so close to just right for most programming projects, particularly, those involving network and systems programming.

You do not need to choose a package manager, transpilation tools, or even a coding style - those are not interesting problems to solve. There is no requirement for your target execution environment to have the correct version of a particular runtime and dependencies. You can focus on solving concurrency problems without having to solve the system-dependent problem of parallelism (managing OS threads running across multiple cores). Go is interesting (and fun) because it avoids the annoying ceremony of how to go about doing things, and lets you just do things, right away.

... but, as previously stated, the engineering problem to solve is primarily what must determine which tools are best suited to solve it.

- Identify gaps in language support or tooling related to our software stack and present a plan to address it
- Ideally, make a case for why this will be useful for other projects going forward
- Present a plan for bringing other team members up to speed on the language you propose to use. Usually the language proposer will have to captain the process of language education.
- Since it's a big decision, we should try to get input from all devs before making a decision at least till our team gets bigger
Copy link
Contributor

@gammazero gammazero May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a big decision

Hopefully, it will not be a big decision, because the engineering requirements and design will naturally select a set of tools that best enables the creation, deployment, and maintenance of the designed solution.

Who knows, maybe Erlang will be the best because of its ability to hot-swap running code. 😉

#### Some Notes On Our Javascript

- Thankfully this team has unified on a coding style, linter, tool chain, package manager, and approach to typing. +1 on that.
- We have not however documented why we made the choices we did. We should do so ASAP. In particular types in JSDoc style comments and why we do it needs documentation. This includes our sporadic dropping into Typescript and why we do it. (For example: 'why is there an api.js and an api.ts and the js file just says `export {}` -- the answer is 'cause modules' but is EXTREMELY not clear)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short answer: best of both worlds.

Longer answer:

  • Fully enforcable type checking in JS without adopting a new language.
  • No build step.
    • Easier debugging because no source maps are required.
    • Allows anyone to depend directly on the repo and/or a branch/tag i.e. npm install git://github.com/foo/bar.git.
    • Makes using local changes to modules in other projects easier in development npm link mylocalmodule.
  • Fringe benefit - not everyone codes with typescript - bigger potential pool of community developers that can help fix bugs, add features, and maintain.

In regards to the api.ts files - it's actually not necessary but it is convenient and less verbose to define domain/API types (Note: only types no code) in TS and then reference them from code.

api.js simply allows you in JS to import * as API from 'api.js' and reference types in jsdoc comments like API.Foo and not get a runtime error. Otherwise you have to reference as import('./api.js').Foo which is a little more verbose.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually have figured out rationales already over time. I'm saying they should be documented somewhere

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 yes I figured, was more for others ;)

I also added to our notion - forgot to put the link! https://www.notion.so/w3sat/Why-jsdoc-types-and-not-Typescript-eb6990cb04c94bebacdda6f2b5ce266a?pvs=4


- Rust is a better C/C++. Like definitely much better. This is huge cause no one wrote a better C/C++ for low level programming for 20 years.
- However, by and large, you should probably not write programs in Rust unless you'd consider writing them in C/C++. There is minimal evidence one can be as productive in Rust as higher level languages.
- In particular, you should probably not write web servers in Rust or any other asynchronous programs unless you have extra time on your hands which we don't.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find some of the statements about Rust inaccurate. I find myself a lot more productive reading and understanding Rust code than I do with go as it's type system forces you into specific design patterns. All the PL go code I had to deal is a nightmare in comparison, no comments terrible naming (one or two letter variable names 😅).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great article describing why discord has switched from go to rust https://discord.com/blog/why-discord-is-switching-from-go-to-rust

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was an interesting read. Thanks 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great. Also, the async library has come a long way, and its progress is very promising.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good counter-example... but is also a lesson when porting from javascript to Go/Rust/etc. might be a bad idea. https://www.youtube.com/watch?v=_SzvJJ3_6M0

The gist is that a huge amount of time and effort was spent to arrive at an outcome with much more code that offered no significant advantages.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gozala

The Discord example is an argument that when a code path becomes super hot, you can get more optimization by switching from a garbage collected language to one with manual memory management.

This feels both correct and also somewhat trivially obvious. Again this is where my "consider rust when you'd consider C/C++" statement comes from.

The more interesting question is when does a code path become hot enough to merit this sort of optimization, and what is the cost of trying to do this for all code paths, even the ones that don't need it.

Caveat from Hannah: I am not a highly experienced Rust dev

- Rust is a better C/C++. Like definitely much better. This is huge cause no one wrote a better C/C++ for low level programming for 20 years.
- However, by and large, you should probably not write programs in Rust unless you'd consider writing them in C/C++. There is minimal evidence one can be as productive in Rust as higher level languages.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what the basis of this assessment is ? Rust can be highly productive high level language as long as you grok resource ownership model. I would even argue that forcing you to think about ownership is a good thing and a perfect pairing with systems utilizing object capability model (like UCANs) because it's all about who owns the resource and who is given access at time.

Copy link
Contributor

@joaosa joaosa May 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also agree that really depends on what you're trying to achieve. When I was first learning Rust, I rewrote a bunch of my personal organization scripts in Rust and it felt quite productive.

That's an interesting consequence of using Rust I had not thought about. Kind of reminds me of Conway's Law, but in a programming language perspective.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Productivity is subjective. I think it might be helpful to get the experience of people who've shipped large amounts of production Rust code -- I think the closest folks in the PL world to this are the FilOz / FVM people -- I can reach out to @Stebalien for reaction :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on a bunch of factors.

  • In the FVM, we had some issues because wasmtime needed a static reference to some state and we didn't want to have rc/refcells all over the place. But, once we got past that issue, it wasn't too bad.
  • In the builtin actors, we had a couple of issues translating existing go idioms, but wouldn't have had any issues if we had written them in rust first.
  • If you dig too deep into the type system, there be Balrogs. You will find things that really should compile but don't because rust just can't quite reason about your types.

The biggest drawbacks I see are:

  1. Compile times and test time. The former is generally an issue if you're have a complex build pipeline with lots of deriving, macros, generics, etc. Especially if you have many build targets (e.g., the builtin actors). The latter can be an issue because, e.g., if you test with an unoptimized build (to improve compile times), tests can take a while in some cases (was also a problem in the builtin actors, fixed by compiling specific crates with optimizations regardless of the build target).
  2. Learning curve. This is something people get over, but it does take a little longer to onboard new teammates.
  3. TEMPTATION. The type system is very expressive and it's easy to go down rabbit holes. This is one of the biggest dangers I've seen in rust because there's always a good reason and the issue is only obvious in hindsight. Really, trying to find the perfect abstraction is probably the biggest rust time sink.
  4. Async/await. IMO, it's still not ready (from what I've seen). It's getting better every release, but it still gets in the way enough to slow development down.

On the other hand, I absolutely love rust and write most of my personal projects in rust (those not written in Elisp). Mostly because:

  1. Performance is very consistent. E.g., when we switched from the "legacy" VM to the FVM, block times, GC spikes, etc. got much more consistent in lotus. A lot of this is simply because rust forces you to think about resource allocation and lifecycles, where as go requires some more discipline.
  2. The type system is very expressive. We've had very few bugs in the FVM because we can often statically prevent them. But you have to be careful to not waste too much time trying to statically prevent all bugs.
  3. The meta-programming and operator overloading allow for some fun patterns when you have common patterns you need to abstract away (they can also be a time sink if you put too much effort into them up-front).

I actually do write high-level tools in rust if I need them to just work and it generally doesn't take that long. There are lots of really great high-level libraries for writing CLI tools. I used to write them in python, but then getting them to work reliably always took a while (need to write tests, etc.).

Rust only starts to slow you down when you get into more complex projects and start building out complex abstractions. And, TBH, a lot of the trouble is self-inflicted because rust lets you build those abstractions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ @Stebalien thanks for taking the time

Folks I think this is worth reading

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • In the builtin actors, we had a couple of issues translating existing go idioms, but wouldn't have had any issues if we had written them in rust first.

I actually was going to comment something along those lines but restrained myself. Going from TS to Rust would feel a lot more at home than going from TS to Go or from Go to Rust. In fact most of our TS actually feels more like Rust than JS because we put a lot of care into resource management to keep memory use at desired bounds.

TEMPTATION. The type system is very expressive and it's easy to go down rabbit holes. This is one of the biggest dangers I've seen in rust because there's always a good reason and the issue is only obvious in hindsight. Really, trying to find the perfect abstraction is probably the biggest rust time sink.

I could not agree more! Most typed languages (I would not put go into this group) it's really easy to fall in the trap of trying to increase safety by capturing more in the type system. I find Rust and TS both to be particularly luring there, but then again solution we often resort to is: Dont' abstract until you have an actual use case for that abstraction, or put it differently if you have one implementation of the abstraction that abstraction is just a means of indirection and unnecessary complexity.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, don't spend a bunch of time re-writing your abstractions. Yes, I'd agree that TS is similar in that respect (and not in a good way).

- In particular, you should probably not write web servers in Rust or any other asynchronous programs unless you have extra time on your hands which we don't.
- BTW, if you haven't programmed in C/C++ recently, you've probably forgotten that long compile times are a thing on large projects in low level languages.
- Rust is essential for highly performant synchronous compute tasks. That's why people use it for smart contracts, hashing, and proofs.
- Rust has the best WebAssembly story of any language.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust is a great tool for writing libraries that you may ever conceive running elsewhere be it native code or web, no other language offers this. I know there are some efforts to make go better there, but GC is a sticking point. Rust has no GC which is why it's good fit and has no overhead, if you have to bring your GC with you that will be a non-trivial overhead and it would need to integrate with a host GC also. Maybe one day WASM will get a GC, there is certainly an effort to make it so, but I would not make any decision that assumes it, because chances are it's not going to be ready in time.

Copy link
Member Author

@hannahhoward hannahhoward May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I 100% agree with this.

If you want your code to run as a library in another program, Rust is the go to.

In addition to having a compelling engineering or business reason, we have some other things to consider before we adopt a language:

- UCAN libraries and tooling especially for anything with a web interface
- IPLD/IPFS/Libp2p libraries (we may not always need libp2p going forward)
Copy link
Contributor

@Gozala Gozala May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this write up overlooks one important reason why we have sticked to JS:

  1. All the cloud services CF, AWS (pretty much anyone else) have a best support for JS and some support for other languages. And if we don't like centralized services, PLware station is also JS.
    • We may reduce dependencies on such things in the future, but flexibility to use them in tandem is important IMO.
    • Rust is gaining momentum in all of those, mostly because of WASM and because those cloud services use Rust themself (as they need to scale)
  2. We wanted a flexibility to move some of the code that rans on our servers into clients, reducing our costs and making system less centralized. It had been fairly trivial given that we chose to use JS and limiting ourselves to web APIs (as opposed to say node APIs). You can get some of this with Rust but even then experience of bundling WASM has not been a smooth sailing.

I would also consider IPVM even if Fission is going away I keep hearing IPVM will carry on and so will everywhere.computer Even if those projects discontinue there is a lot of excellent code that was written for those that could be a great foundation to build from.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fundamentally I need more signal on IPVM continuing onward before I'd commit anything we do to using it.

Copy link
Member Author

@hannahhoward hannahhoward May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree on Javascript portability.a

"Use javascript when your code might need to run in a browser" could probably expand to "Use javascript when you really don't know where your code might run but you want maximum portability". Though the native / bare metal story is a bit rougher.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use javascript when you really don't know where your code might run
... but you can be certain about the runtime environment (in browser, node-js version, etc.)

Use go to build executables when you know nothing about where it is running, only that it is not in a browser.

Copy link
Contributor

@Gozala Gozala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided some feedback inline.

Copy link
Contributor

@joaosa joaosa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally agree and left a few comments to other people's comments

- Ideally, make a case for why this will be useful for other projects going forward
- Present a plan for bringing other team members up to speed on the language you propose to use. Usually the language proposer will have to captain the process of language education.
- Since it's a big decision, we should try to get input from all devs before making a decision at least till our team gets bigger
- Must have sign off from senior engineering leadership (for now Hannah and Alan)
Copy link
Contributor

@gammazero gammazero May 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO - I can see a team being productive as long as service tooling (like async programming, etc.) is solid, I could see this being our chosen language for writing services, even over Go in situations like Discord that made Rust such a success for them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my concern with Rust has a lot to do with the speed of development for new code, as opposed to optimizing already well prototyped services.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe @mroth wants to weight in here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One concern with Rust for larger projects is that it can slow down iterative development. There is more code, due to the need to precisely manage resources, among other things, and refactoring tends to be more difficult. With Go, it is relatively easy to refactor as a project evolves, allowing for rapid iteration of development cycles.

In addition to longer refactor time, rust compile times are significantly longer than with Go. The difference becomes pronounced as the amount of code and dependencies grows. This makes testing and trying out new code quicker in Go, resulting in considerable time savings over the course of development.

This echos what was already said by @hannahhoward, and is in the spirit of "consider rust when you'd consider C/C++".

Rust is an awesome language and enables one to write highly optimal and safe code. However, I think it is a mistake to fall in love with this without also understanding the value of what Go beings. That is: Go does everything it can to minimize thought and time spent getting the compiler to do exactly what you want, allowing you to focus on best expressing the logic needed to solve whatever problem you are trying to solve. There is some cost to that (like GC), but the benefits of productivity and clear maintainable code very often far outweigh that cost.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactoring tends to be more difficult

It depends. Sometimes a structural change is difficult due to the ownership model. On the other hand, sometimes a refactor is easy because, once something compiles, it usually works.

Bug I agree that iteration cycles in rust tend to be slower. E.g., I wouldn't prototype a project with an unknown architecture in rust if I could help it. I'd start by prototyping and answering all the architectural questions in Go, then I'd rewrite it in rust.


One more thing for rust, however: C compat. CGO is a nightmare.

@hannahhoward
Copy link
Member Author

An interesting piece on Rust development from folks who've been doing it a while -- https://llogiq.github.io/2024/03/28/easy.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants