TypeScript as fast as Rust: TypeScript++
zaplib.com
zaplib.com
Low-level operations like manual memory management are not like that. They are part of the runtime, and _do_ affect the business logic. It's a leaky abstraction. So if I import some other team's Rust code and I try to write RustScript on top, I might have to worry about some special edge cases where the argument I pass in isn't properly garbage collected or what not.
For example, Rust used to have a garbage collector. A language set boundary would be a good way to add this back in without requiring a runtime at the lower levels.
Rust already has “levels”, such as no-std or core.
Similarly C# has a scripting variant: https://visualstudiomagazine.com/articles/2021/06/14/csharp-...
And it also supports AoT compilation to a single EXE, but there were a lot of limitations. The latest attempt is a WIP: https://github.com/dotnet/runtime/issues/61231
I would argue that libraries especially need highly restricted language subsets. E.g.: an image parser library should be “pure” in the sense of never being permitted to access system APIs. Image binary in, decoded bitmap out. Like a pure function, but at the module level.
NGEN which was there since day one, but its main purpose was fast startup and only allows for dynamic linking.
Mono AOT one, used by Xamarin workloads for iOS and Android deployments.
The Sing# and System C# dialects used by Singularity and Midori respectively.
The MDIL used by Windows Phone 8 and 8.1, based on Singularity's Bartok compiler.
.NET Native introduced in Windows 10 for UWP workloads, replacing the MDIL based one, originally known as Project N.
The NativeAOT pointed by you, which started as a research project on how to bring .NET Native back into main .NET.
Finally some alternative ones like Unity's IL2CPP or CosmOS.
Verification may be interesting, for high-assurance (e.g. automotive/aerospace) applications. It's for this reason that a theorem prover[2] and an ml[3] have been implemented, and they interoperate freely with the rest of common lisp.
In other words: we are already where you want to be, and we are even less stratified than you propose we would need to be.
0. https://github.com/marcoheisig/Petalisp
1. https://github.com/froggey/mezzano/
Also maybe of interest, not quite the same but on vaguely parallel lines, minikanren and microkanren.
Python+mypy+cython
https://cython.readthedocs.io/en/latest/src/tutorial/memory_...
https://github.com/orta/awesome-typescript-derived-languages...
I support efforts to tune existing tech stacks. But there is such a more direct path to performance, reliability, etc etc by dropping (alt)JS entirely now that anything can compile to WASM.
However, at this point, I'm mostly of the opinion that langs need some notion of mechanical sympathy if they want to be used for high-performance. JS engines probably have tens of millions of dollars of dev effort poured into them by now, yet they get soundly beaten by WASM, which is much younger. The conceptual foundation matters a lot.
That said, there's probably a nice space for an ergonomic, reasonably fast PL that sits between Rust and JS.
And JS engine JIT codegen modules are put to good use compiling wasm.
I'd written off Dart as "Google's Swift" for the most part, but this makes a lot more sense. Thanks for the comment.
Other than not running natively in web browsers, isn't that language called Ocaml?
My initial instinct is that putting such considerations behind a preprocessor syntax has the disadvantage of making them more opaque and magic, and that I personally would continue to do such things more explicitly - which I realise is an ironic thing to say about an untyped, interpreted language, it's probably just the little pseudo "grey beard" on my shoulder which occasionally whispers silly things about "building their own computer out of rocks and twigs".
I guess the point is that there's a balance to the cost vs benefit: making it more convenient (what you call ergonomics), making it more accessible to those that were less likely to manually use that technique, and perhaps resulting in an average of more performant code; but at the cost of more obscurity, and more "magic", perhaps resulting in less educated programmers.
I'm probably overthinking it. Ultimately this argument even applies to for loops, but it's worth being conscious of that cost - it's likely a good idea.
> Another idea to make Typescript–– a bit less restrictive and more ergonomic, would be to use reference-counting of objects, and then try to optimize most away using compile-time reference counting as pioneered by Lobster. This gives most of the advantages of manually managed memory, but makes the ownership model much easier to reason about. It is however slightly less performant, and more importantly, makes performance less predictable, since it becomes more reliant on compiler cleverness.
Even Rust can hide actual performance behind abstractions, and I've already been bitten by that a few times.. Not sure what the right solution is here, but I agree that it's super important to think about this when designing a language like this.
What projects were you working on that required that? It sounds interesting
Beyond that, it's more of a personal philosophy than a requirement (I could easily have done all those things more carelessly, but the result wouldn't be so nice, some people's laps would be hotter, and some people would end up with a slow or jerky simulation). I like things to be efficient, I enjoy finding a balance between "efficient" and "minimal". That doesn't mean I avoid GC all over the place adding unnecessary complexity and over optimizing, it just means being considerate where it matters and "idiomatic" where it doesn't - in a physics engine (or any kind of posteriori simulation) it matters because it's running on an interval and will constantly cause perceptible GC hiccups if you don't be considerate with memory.
It's certainly not helped by the latest language fads just completely flooring the GC gas pedal; special mention should go to for...of creating a brand new object for each loop iteration. That's usually the first thing I rip out of tight loops.
A bit of care can make this stuff go 3x fast on PC, and I've gotten completely unusable experiences on mobile (most websites) to be 30-40fps.
Maybe there are some ideas there.
I was doing some high-level research on a related question the other day: https://transitivebullsh.it/webassembly-research-9e231b80e6d...
I know you've responded to a few people on here that Assemblyscript doesn't address the main issues you're talking about in the article, but honestly it does have a lot of things going for it.
I'd love to see a more direct breakdown of this space that explains why prior works like assemblyscript, nectarjs, walt, etc all fall short, and where an ideal solution would need to do better.
^^ this is the type of thing I'd love to chat about if you're interested btw
It doesn't look like you're making the same mistakes, but why trigger that prejudice?
I suspect that if you could come up with an easy way to express a large tree really efficiently in a TypedArray in a relatively generic way, that you already will do better than most attempted solutions here.
type Vec2 = { x: number, y: number };
function avgLen(vecs: Vec2[]): number
versus:
struct Vec2 { x: f64, y: f64 }
fn avg_len(vecs: &[Vec2]) -> f64 {
There has been more than two decades between the release of Perl and Rust and you haven't learnt anything? Wow. Listen: code written in languages heavy with sigils and shorthand are hard to understand, read and most importantly: maintain. This adds a mental load which is -- as clearly visible from above -- is totally unnecessary. I have no idea why would anyone in 2010, several years after the famous "memory is the new disk, disk is the new tape" do this to save on characters in the source code. The teletype is a distant memory.
Thanks for letting me vent a little.
> This adds a mental load which is -- as clearly visible from above -- is totally unnecessary
For numbers in Typescript you basically have a number type (there is also that story about BigInt, but...) - this is as good as Rusts f64
In Rust you have: u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64
Sure, you could have some kind of alias number = f64 - but why? Rust and Typescript have different uses.
For the web, number (f64) is good enough. If you do system programming then you need some options.
You could also write the function above as `fn avg_len(vecs: Vec<Vec2>) -> f64 {`, and I personally would write the TS one as `const avgLen = (vecs: Array<Vec2>): number`. However, there's a difference between the ownership of a `Vec` and `&[]`, something a language like TS doesn't need to worry about but Rust does.
Regarding shorthands, just number would be unacceptable for 99% of the work I do, and it would be something that specifies signedness, integer vs float and size. As a c++ programmer, having to write std::uint64_t gets old very quickly and I'm happy that rust uses a shorter name for these.
I don't think that using a shorthand for something very common is an hindrance to comprehension. Do you think one should write "have not" instead of "haven't" in English?
Given how many people do this wrong? Yes, that sounds like a great idea.
I have to agree with OP that Rust often looks like sigil soup. The example isn’t great, but there is a _lot_ of noise when looking at Rust code. Of course they all have a meaning, but I feel sometimes that meaning could be more easily expressed as a verb than a sigil. At least it would make things more readable (until you get into generics I think Typescript scores pretty high on readability).
Why not try to come up with an alternate syntax, expressing everything in English-like verbs and compiling into Rust proper? You could call it RUBOL. Then #[RUBOL] code fragments could even be supported within .rs files
As someone who maintains a 145k line Rust codebase all on his own, I have to hard disagree on this one.
Once you get used to the syntax it just blends into the background and becomes a non-issue.
> This adds a mental load which is -- as clearly visible from above -- is totally unnecessary.
Totally unnecessary? Okay, so how would you do the following with the sigilless syntax (and disambiguate between them):
- Specify a unique reference? (`&T` is shared, `&mut T` is unique)
- Specify a lifetime? (`&'a T` is a reference to T with lifetime `a`)
- Specify a raw pointer? (`*const T` and `*mut T` are const/non-const raw pointers)
- Specify that the argument is moved into the function? (just the raw type `T`)
People have been trying to come up with a better syntax for Rust, and AFAIK so far nobody has been able to do it. There's always a tradeoff somewhere that ends up making matters worse than what we have now.
shared T
shared mut T
> Specify a lifetime? (`&'a T` is a reference to T with lifetime `a`)
shared T lifetime a
> Specify a raw pointer? (`const T` and `mut T` are const/non-const raw pointers)
const point T
point mut T
> Specify that the argument is moved into the function? (just the raw type `T`)
T
I do disagree on the syntax for the references though. I prefer they be easier to type than the owned versions (eg &[u8] vs Vec<u8>) as this can promote the usual better choice (this is a weak argument, I acknowledge).
I do wish that history had chosen a different symbol for references though. But there's only so much we can do now
The more complex the underlying semantics you're working with, the more verbose and less symbol-driven your language should be. You can see this clearly with things like COBOL, which tried to integrate every feature that might ever be needed for business programming within its base language - a staggering amount of complexity. English-like syntax kept that workable.
Nit pick: Every word in this sentence in the article is a link. This way of linking multiple related resources is so hard to keep track of and navigate. I need to hover over or click each word to discover the resource.
I just middle click to open each link in a new tab. I can read the new tab, close it, leave it open, whatever. The links I've already clicked will turn purple, meaning I can easily see which links have been visited already.
Is there something I'm missing?
Note that Microsoft actually has a compiler from a Typescript subset into C++, as part of the MakeCode IoT education project.
A compiler is a compiler, regardless of the target language.
Are you aware originally C compilers did not generate machine code directly, rather Assembly source and then called the Assembler on it.
C++ and Objective-C initially generated C.
Eiffel to this day generates either C and bytecode.
Nim and Haxe are another examples, and so forth.
for (const vec of vecs)
Instead of: for (const vec in vecs)Mnemonic: of = objects fail, in = index names
Yeah yeah objects can be arrays and iterable but you know what I mean. Best I could do! If you dont like just use the “in” mnemonic and Sherlock Holmes.
Worked ok for a while but broke down real quick once we brought in external npm dependencies.
Now we're just embedding V8 in our native apps and running the TS code through that. Not as interesting, but lets us import any random npm package (which is both good and bad).
IME this can greatly improve cold starts.
I think these cases will just never be good for WASM. DOM manipulation heavy Apps (which is a good chunk of JS code out there) may continue to be in JS and only the CPU intensive tasks would be computed in WASM.
Isn't that the best practice for WASM?
I wouldn't recommend dropping to WASM until you've ironed out the ideal data structures/algorithms to use, and have spent some time in the devtools profiler. Hacking on ideas in JS is (IME) much faster than doing the same in a compiled language, and the built in profiling in browsers works far better for JS than any WASM (again IME). Only after you have an optimized algorithm, data structure, and implementation, and still find perf isn't where you want it would I recommend rewriting in a compiled language -- even then I'd wager having the optimized reference implementation around will make this much easier and faster than if you tried to start from 0 in the compiled language.
Bonus points if you can reuse tests and/or have a debug mode that runs both implementations in parallel and throws on any differences in output.
So it's either wasm or something like scala.js or fengari.io (lua on the web)
(Worry not, I am paid to write JS and TS these days... But my rate increased due to the pain it causes ;))
https://hacks.mozilla.org/2019/08/webassembly-interface-type...
So the number of projects where there is enough DOM interaction that WASM will be slow, but not enough that JS is still usable is very small.
I'd say a lot of the ergonomics are there, but not every pattern has been translated cleanly from React into Rust yet.
[1] https://github.com/DioxusLabs/dioxus/blob/master/packages/in...
https://haxe.org/manual/types-abstract.html https://code.haxe.org/category/abstract-types/color.html
Might be worth clarifying because I immediately got excited about a faster TypeScript compiler!
I agree untyped for more developer speed is a bad joke.
You also speak as if speed of development/expressivity are in direct opposition of maintainability and structure, which just isn't true.
But it has the potential to go beyond, it just needs marketing. It worked for Go (from a Google-internal language to replace C++ to a widely used popular language), Rust (from a Mozilla internal project to solve their issues to a widely used and popular language), even Typescript (as a replacement for flowtype, coffeescript (?), atscript (that was a thing) and some others, again developed, sponsored and promoted by a big organization (Microsoft) after being necessary for their large scale web applications (Office).
anyway, Swift uses LLVM to compile so OS-specific libraries aside, cross compilation shouldn't be a problem - even to WASM.
[0] https://kotlinlang.org/lp/mobile/
[1] https://kotlinlang.org/docs/whatsnew1620.html#concurrent-imp...
Impressive that someone familiar with the usual JS development toolchains can write this and keep a straight face...
yarn create next-app --typescript
... and I am done yarn create react-app my-app --template typescript FROM node:16
Done (including dev env with VSCode dev containers).And if you created a Next app as I suggested, your CI config is super-easy - you need to place 3 commands into your YAML and that's it:
yarn lint
yarn build
yarn startAnyways, cargo and rustup are cool tools, I don't want to criticize Rust. I just don't think the JS tooling is so outlandish as people make it seem so.