Hacker News new | ask | show | jobs
by byteofbits 2173 days ago
As a datapoint for your hypothesis, when working in a typed language I will build my code only a few times a day.

I much prefer working from a logical and thoughtful approach rather than iteration. At the point where I start a build I am already reasonably confident that it will do what I want it to.

There are some bugs where I will need to re-build several times consecutively but these are relatively rare for me (I work on REST API systems - so nothing too crazy).

8 comments

That's really interesting. Compiling only a few times a day is mind boggling to me - in a normal day I'd probably average hundreds to a thousand compilation cycles!

The main thing that I use compiling for is to validate little off-by-one things. Like, is substring() exclusive on the second parameter? What about range syntax and the slice operator? What if I wrote + 1 instead of - 1 somewhere, or did < instead of <=? I could spend a few minutes combing documentation, or just compile it and check immediately. Well, assuming compilation is fast anyways.

> The main thing that I use compiling for is to validate little off-by-one things. Like, is substring() exclusive on the second parameter? What about range syntax and the slice operator? What if I wrote + 1 instead of - 1 somewhere, or did < instead of <=? I could spend a few minutes combing documentation, or just compile it and check immediately. Well, assuming compilation is fast anyways.

I'm a really big fan of reading the docs (or, where the docs are insufficient, the source), and will always have docs.rs open alongside my editor. While the examples you provide are easily validated by testing (and I'd probably use `evalr` in `##rust`, or some similar thing, for them), many subtleties can exist in more complex functions, so not reading the docs/source for unfamiliar functions feels like programming-by-guessing to me.

Perhaps my examples were a little too trivial. I do a decent amount of game/graphics coding, so there's a lot of "hmm, does this look good 20 pixels over? How about 18?" and I don't know how you'd get around that without recompiling.

(OK, you could read preferences from a file, but then you'd have to optimistically write every value you'd ever want to recompile to a file. I've tried this, but it's far too much overhead.)

Why not have controls in your program to do these immediate, non-logic changing modifications? It can report the ideal value through a debug interface. No recompiles needed.

I'd also recommend getting used to serialisation/deserialisation. Serde for Rust makes this remarkably easy. Writing every setting to a file is simple if the compiler can do it for you, rather than you walking the long way around.

When you're doing any sort of non-trivial gamedev, graphics, or physics simulation work, there are so many instances when you have to get a bunch of magic numbers by trial and error, and it's usually all over your code. If you try to "refactor" these cleanly into a separate JSON file (hint: it NEVER is that clean), it still takes time and effort away from you to by writing boilerplate code, which seriously kills your momentum for experimenting and iterating with your code. It's kind of a niche domain-specific thing which most gamedevs and graphics programmers would sympathize with.

Also there are also a shit-ton of minor logic-changing modifications when you're writing prototype gamedev code, (for example, moving an if statement here or there to tweak the physics of your platforming). These cannot be easily represented in config files, and this is why people attach lightweight scripting languages like Lua to their game engines (so that you don't have to wait for those atrocious C++ compile times when tweaking your gameplay code).

(Ironically, serde is known for its atrocious compile times, which really makes the situation even worse. Because of this some frustrated Rust gamedevs wrote their own serialization libraries such as nanoserde (https://github.com/not-fl3/nanoserde)... but you get the point.)

FWIW, I am with you in the camp that cares about compile times. I use C++, I care deeply about compile-time correctness (and have "as close as I can get to rust as I can" abstractions for tons of things such a small locks, but then take advantage of the almost-dependent typing provided by C++ to go even further than you can in rust to prove correctness of my buffers), and I envy people who get to code in Haskell or Idris. I am not a "dynamic typing" addict by any means.

But I work on network protocols and file formats and parsers and video transformers and user interfaces (often command line or web) and a long time ago games ;P... I make a small change and then I want to see the behavior difference. I almost always type code that compiles, but that doesn't mean it did what I want when interacting over the network: rust doesn't provide anywhere near powerful enough type abstractions to actually prove my code is correct... it only is proving that my code can't crash and will avoid undefined behavior.

What is so frustrating is that there is nothing about Rust that makes it impossible to make fast. With C++ I get to manually make tradeoff in my code on a file-by-file basis with an assumption that my project will be built out of thousands of mostly-independent fragments that I am able to compile incrementally or in parallel. I can very rapidly isolate individual functions I am working on for fast iteration without having to restructure my project, I can tell the compiler to instantiate templates in different ways to reduce dependencies, and I have designed a system to let me do parallel compiles on AWS Lambda (I haven't yet gotten my build environment to be ready for a -j1000 parallel build, but I am somewhat close).

I have been integrating some rust libraries into my codebase (I haven't even gotten to the point of really coding in it omg) and Rust is just so painful and frustrating to work with... and it really does just seem to be, as you pointed out, a matter of priorities and interest: there is this myth that it is some kind of tradeoff on the type system, but it isn't; it is just that the people who work on rust seem to not work on the same kind of projects that we do, at least in the same way, and so have made a bunch of trade offs like "I would rather never have to type a prototype than save any time compiling" and "I would rather my build system never have to consider dependency management than save any time compiling" and "I would rather provide the highest possible quality resulting binary than save any time compiling" (which is at least one I can appreciate, but only once a month when you cut a release... not the hundreds of times a day that I recompile my C++).

> "I would rather never have to type a prototype than save any time compiling" and "I would rather my build system never have to consider dependency management than save any time compiling" and "I would rather provide the highest possible quality resulting binary than save any time compiling"

Rust outputs prototype information in .rmeta artifacts that are generated as one of the first steps in compilation, so this is not a compile-time issue. AFAICT, dependencies are also tracked, and debug vs. release switches are provided that also control things like binary optimization.

The real "problem" for compile times in Rust is that Rust makes it idiomatic to write code that's slower to compile. That's really all there is to it. If you were to literally write Rust like it's C, you'd find that there's no real overhead introduced by Rust per se.

(This is not to say that improvement is not possible, of course. Even what's "idiomatic" can be tweaked over time to reduce the amount of excessive, duplicated work that the build system has to do. Newer features like const generics will probably make this feasible in the future, and improvements in the compile workflow itself will do the rest.)

Yes, for example I can easily do multiple UI changes in UWP, with C++/WinRT or C++/CX, in a fraction of the time that it takes to build Rust applications.

I am quite curious how usable Rust/WinRT will turn out to be for WinUI work.

> I do a decent amount of game/graphics coding, so there's a lot of "hmm, does this look good 20 pixels over? How about 18?" and I don't know how you'd get around that without recompiling.

I know exactly what you mean. Front-end development can be this way, too.

I think it's OK (and probably true) to say that you shouldn't use Rust for cases like this right now.

Yeah. It's a bummer because Rust would be a fantastic language for gamedev otherwise, and a lot of people are interested in it for this reason. It's got everything else you'd want out of a good gamedev language - no GC, smart memory model, blazing perf, really expressive...
> I think it's OK (and probably true) to say that you shouldn't use Rust for cases like this right now.

I think that's true, but only because of the slow compile times. Which is why they're so frustrating. Rust would otherwise be an excellent language for these use-cases.

I see where you're coming from. The good news is that compile times are something we are certain will get better. Rust's maintainers know it's a high priority, but even if they didn't, we would still benefit from increasingly powerful machines used for graphics/gaming development.
That’s the sort of thing where you often use another language suitable for hot reloading for such customisation or rapid-feedback alteration. As a couple of examples I’m aware of:

• The Azul GUI toolkit uses CSS for styling of its widgets, and hot-reloads stylesheets.

• The Mun programming language is designed for the sort of niche Lua is often used, with hot-reloading scripting-like functionality to augment your Rust code. (Well, Rust is the main host language at present, anyway.)

Unity build times on a huge project aren't hot, either; particularly when you must build a client to test.
Yeah, for game/graphics stuff I can definitely see that.
> The main thing that I use compiling for is to validate little off-by-one things. Like, is substring() exclusive on the second parameter? What about range syntax and the slice operator? What if I wrote + 1 instead of - 1 somewhere, or did < instead of <=?

Rust has a testing facility that can be used for these things. By default, all "example" code that's included as part of a doc comment ends up in the test suite. And because test cases are small and self-contained, they're also very quick to compile.

I've to say, I really hate this way of working, which might be the reason why I prefer statically typed languages like Rust.

I think the main reason why I dislike working with dynamically typed languages is that most of them make the reasoning of code a lot harder. If you're able to reason about the code at hand, then there's a lot less need to run the code for every small change.

>> in a normal day I'd probably average hundreds to a thousand compilation cycles!

Maybe it's time to switch to an interpreter ? :-)

Or a language like Go, C or Nim that can compile tens of thousands of lines of code in a few seconds.
You forgot about D, where you can basically bind ctrl+s to rdmd in your favourite IDE and enjoy scripting language akin experience.
D is sadly very underrated.
My day job is in C and compile time is 30 minutes. It really depends what you're working on.
Does it take that long for an incremental build?
I've used Nim just a bit on Windows/MingW (I'm from python world). The very short program I made (about 200 lines, mostly math stuff, no framework imports) compiles in 2-3 seconds. It's already too long for me to iterate (it's not a rant against Nim, it's just that it's too long for my way of working; the language is nice and gives good results). Also note that Nim compiles fast, it's the compile-to-native compilation step that takes 90% of the time.
Or OCaml, Java, C#, F#, Eiffel, Delphi,...
I've worked in a C# code base where compiling the solution took 40 minutes.
Wow, one of our solutions is nearly 100 projects (legacy) and the compile time for the whole solution is sub-5 minutes. I'd love to see what kind of solution would take 40 minutes to compile! (or maybe I don't :)).

That being said, C# is clever enough to only need to recompile the assemblies affected by your code change, so often you can get away with 10 second compile times even for large solutions.

Just googling “f# compile times” would beg to differ.
Apparently you didn't bother to read the 3 answers that come up.

Only one of them says anything and it an empty statement like

> F#'s compile times also seem to err on the long side but not very much so, my impression goes.

However it is quite easy to validate write the same algorithm in F# and Rust and then compare.

Ah but Rust is AOT compiled, easy, use NGEN, .NET Native, or Mono AOT for the F# compile time measurements.

You mean a REPL, or an interpreted language?

I'd love a REPL that could somehow load the state of my entire app so I could test stuff out, but I've never found a language that could do that and also had reasonable type safety guarantees.

With Jetbrains IDEs (IntelliJ, Webstorm, CLion) you can run a "sketch" that can "see" any module in your project. I use that a lot in Java/Groovy/Kotlin/Rust and it feels much superior to a REPL because I can write not just one-liners, but larger amounts of code, with all the features of the IDE like auto-completion, inline docs, syntax checking etc.
A test suite facility can also be used like this. And Rust adds any "examples" you write in the code (with proper doc formatting) to the test suite, as mentioned elsewhere.
A test suite has a different use case... REPLs/Sketches are useful for exploration... both of your own design and of libraries you're using. Tests can be used for this as well, but it's not as convenient when you're very "early" in your exploration or have no code yet to test at all.
Some of the best solutions to that world right now seem to be things like lisps with dynamic typing added. You get a great set of repl support, but you can also build components and systems into easy wrapper scripts to load them as needed.
I assume you mean static typing enabled. Because lisps are already dynamically typed to begin with.
Thanks, I actually meant gradually typed systems, like rust typed or closure with heavy spec usage.
Visual Studio can give you a REPL experience for a number of languages (notably C#, of course) using its Immediate and Interactive windows.
Have you try Haskell? The most used compiler, GHC, shipped with it's own REPL and it's quite good. Something I missed when coding in Rust.
> is substring() exclusive on the second parameter

I can’t imagine not looking up the documentation to do this.

If a language had a REPL I may then confirm it in the REPL, but almost certainly would pull up the docs first.

If you have an editor with syntax checking such as VS Code with rust-analyzer, it checks syntax in the background as you type, saving you a lot of compile cycles. It doesn't yet scale very well to huge projects but getting better every week - rust-analyzer is under very heavy development.
Assuming you take ~4 seconds from wanting to compile til you have your answer, that’s 60 minutes a day for a thousand compiles. An hour. Doesn’t seem very efficient to me.
Not OP, but these things don't have to be synchronous. I sometimes have entr running in a separate terminal, which tests and builds on every save. I don't wait for it to happen, but I do see it if it errs.
As one more datapoint, I do this (=barely ever "compile" / run) even in dynamically typed languages. My primary is Python.

Definitely a leftover from when I was a kid, in the early 90s, without ready access to computers. I remember lying at a hospital bed and filling pages and pages with C64 programs, using pen and paper.

This kinda forced me into the paradigm of "think through invariants and structure first". To this day, typing code out on a computer (incl. compilation) is almost mechanical, not a vital part of the design process.

That's spot on. I mostly work with C#, F# in a day to day job and I build my code only a few times. This is also true when I work with Java, Angular and TypeScript. When I pick a new feature to implement I design that on paper with pencil and then mostly translate that to code. Mostly I can code for hours without compiling since this is where Typed Languages have strength.
I York in C#, Java, and Go and I mostly use a combination of these modes. I will usually take some time to design a feature and write it out almost entirely before even the first compilation. Then, I will usually start a compile-run-debug-edit cycle to fix all of the off-by-one type errors, cover missed cases, the occasional wrong assumption etc.

I don't think typed vs untyped languages really makes a huge difference if you're designing code this way. The difference comes when the compiler can actually verify your design, if you don't compile it doesn't really matter what the compiler can check.

I guess it depends what you're working on.

I mostly work with C#, and occasionally I might only end up compiling the project a couple of times in a day, but I'll probably be building a test project several times in the meantime (e.g. after adding each test).

Alternatively, if I'm building a web GUI, I'll probably be building quite frequently, making sure that data is bound correctly and that the UI looks as expected.

Try to do design GUIs without compiling. :)
I agree that whenever I've to "adjust" something in HTML, CSS, its always a struggle with multiple tries and write-run-write-run cycle. But again, this has nothing to do with how you design and write code in a static language.
Sure it does, for example in SSR frameworks, you want the HTML template engine or UI designer to see those components.
That's very interesting. I can never work that way. I compile constantly (C++)
As a counterpoint, I'm mostly working in typed, compiled languages, and I compile and debug-step through my changes in much smaller increments (usually a couple of minutes). But this only works because part of the work is to always fight 'compile time bloat'.

It is easy to let a project slowly slide into a state where this workflow is no longer possible, especially in high-level languages like modern C++ or Rust, when even incremental builds take so long that it throws you out of "flow".

But anyway, I think this sort of working style doesn't have to do with a language's type system, but is a personal choice and one isn't necessarily better than the other, but certain personalities might be attracted to certain language communities, and thus directly influence priorities (e.g. a large part of the 'modern C++' community seems to think that compilation time and good runtime performance in debug builds are not high priorities, which from my point-of-view is entirely irrational).

I have seen few, often genius programmers do that. but not everyone can have a mental map of the code like that. we just need to run and fix part by part.. may be it's matter of practice, but that's the way several programmers workflows are.
+1 for that data point. The type system allows me to be much more explicit with regard to what I expect my code to do. And Rust-analyzer helps me spot those parts that don't fit together. But I typically turn it off, until I am actually done with defining all data types.

I also use assertions and unit tests, because the type system still has its limitations (or some things would be too awkward to express), but I can move forward a long way without actually compiling a project.

I am in a completely different environment than you, but for desktop dev, I need to recompile constantly to verify alignment of usercontrols and such.

So, for my workflow relatively fast compile times are mandatory.

(Altough, I really wanna get out of desktop dev.)

That doesn't work for GUIs, which in this case would be SSR.