Will Hare replace C? Or Rust? Or Zig? Or anything else?
harelang.org
harelang.org
I don't have to waste time evaluating web frameworks I can just start coding a website with net/http right away. Oh its production ready!? Amazing!
I do wish Go had more things built-in like email and other protocols.
If I were maintaining or creating a systems language, I'd invest in the standard library being powerful out of the box. I really secretly wish Rust / D had a web server baked in sometimes.
All things said, this is the first I've heard of Hare, and it looks nice to me from what little I've looked at.
Rust doesn't seem to have that rich of a standard library, bunch of the official documentation asks you to add crates for lots of things. I find myself reaching for external crates for tons of things I'd expect to be in the language as well.
I also don't think Go's success can be attributed to one single thing, but probably the biggest attribute would be that they are backed by one of the biggest tech company in the world, who have adopted it wholesale and would do so, no matter what, since they built and control it.
Standard library usually comes with strong backwards compatibility guarantees. So it becomes ossified over time. Leading to - standard lib is where libraries go to die.
Without stability guarantee, you risk introducing breaking changes between versions, which is worse. And inferior to just downloading a lib with most stars.
1. Include as much as possible. This has been Python's approach and is showing its limitation: you end up with lots of historical baggages you can't maintain. It took an eon to remove some dead batteries from the Python standard library [1].
2. Exclude as much as possible. This is the preferred approach for most newer languages, which can't readily determine at which direction the language would be heading. These languages tend to weigh more on third-party libraries with their pros and cons.
3. Pick use cases and design the standard library only for those use cases. It would mean that you have a near-total control over the language's evolution, so you can include whatever you want without thinking about its long-term consequence (because you know a new addition would be necessary for your use case). This can be often seen from some smaller languages, but Go is in my opinion the only mainstream language using this approach and that's only possible due to Google's involvement.
I started trying to use something like tokio for async stuff, but found that Rust's standard lib threading primitives were already nice and numerous enough to get a lot of work done.
The rust programming language book has a nice little project of building a multithreaded HTTP web server from scratch. Of course you don't necessarily want to do this for everything, but the fact that a lot of the primitives are present and with good abstractions is commendable!
Go's stdlib stuff for web servers is obviously much easier to get rolling though.
https://doc.rust-lang.org/stable/book/ch20-00-final-project-...
You can check out the stdlib here:
Let me know if you think this strikes the right balance.
(BTW, https://harelang.org/documentation/ is the link for more general documentation about the language.)
Also, no data structures? I don't see maps, arrays, sets. No support for custom allocators (where's the `mem` module)?
The roadman mentions TLS, smtp, sql and http (no json?) but you're not too concerned with people wanting to use it for web development?
Arrays and slices are built into the language, sets and maps are not. Much like C, though slices are new to Hare.
I like the concise names. It's a matter of taste.
pwent and grent have been called that for 46 years, though usually only in the names of functions: https://www.freebsd.org/cgi/man.cgi?query=getgrent&sektion=3
Similarly, chown. Or is your complaint that there are two different chown calls?
Trying to rename these things would make the library a lot harder to understand. Can you imagine if someone decided to call the USA "Samland" because they thought the three-letter identifier was too short? Would that reduce confusion?
I kind of agree about vtable, though.
Arrays are part of the base language, not a module. Not sure about sets and maps.
I attribute Go's success purely by it being pushed by Google.
If we use Rust as an example, it's super easy to include high quality libraries (just add s line in Cargo.toml and you're good to go).
Another example is Ruby and how it was made popular thanks to Rails, or how Elixir is making a splash in web dev thanks to Phoenix.
At the beginning Google's support really made a difference, but the continued traction of Go is really the result of it being a great tool to build network servers where you want a high level of performance.
This was the case from the Day 1. Google strongly shaped the language's direction and that has a continuing effect on Go's success. Put it differently, if somehow we end up with the future without servers Go will have a very hard time.
Golang isn't really faster then any other on the same playing field (compiled and with GC).
This is incorrect; HFT platforms have been written in Java for example (https://news.ycombinator.com/item?id=24895395). But it's about either tweaking GC settings or mode, or avoiding using the heap entirely if you can.
Go is still slow if you (over)use the heap, but at least it gives you the choice and plenty of tooling to analyze it.
I really like the arena libraries you can use in Rust where you can have logic to free untouchable memory at a specific time.
Also some performance-sensitive chunks of wall street itself (depending on how you define "wall street itself". And "performance sensitive" (what I worked on was more "high throughput" than "no pauses".).) run on java.
TBF, I write a lot of java, so my perspective is probably biased, but I can assure you, it's out there.
This is manifestly false. I used to write general graph analysis kernels on supercomputers. We used (old-style) C++ and never had an issue with memory management. Your assertion is assuming a naive implementation that would have offered poor performance regardless.
js actually beats Golang on most metrics if you're aiming for synthetic "web" performances.
Go will be more than fine for the vast majority of servers people need to build. Sure, there are some things where Go might not fit the bill, but that doesn't really matter; Go is still super relevant for writing servers, just as Java (another GC'd language that some people call "slow") is too.
There are very few languages which are categorically slower then Golang, and these are generally languages which explicitly accepted slow executions for ergonomic reasons such as ruby and python
For comparison see Dart which was pushed even more by Google (I still remember when we were being inundated by "dart is soon gonna replace javascript everywhere" posts/videos) and now has not even remotely the userbase of Go and it's just a niche language for its specific framework Flutter
It was only rescued by AdWords team , and then Flutter team picked it up.
The Android team is also not in love with it, and Jetpac Compose is surely an reaction to it.
Unlike Swift or Kotlin that are heavily entwined with the main mobile platforms, nobody needs to use Golang for any particular reason. As far as I can tell its developer experience is pretty good so people have opted for it because it fits their needs.
Assuming a web server—Which library? Which version? How many commits does it have? When was the last one? How long will it be supported? Is it async? How many deps will it pull in? Will it be superseded by a fork? Does that fork have a different API? Will I have to bump the Rust version to match it in the future? Ok, I've gone to another team and their app uses a completely different library, what's the answer to all those questions again? Etc.
With Go, the answer is and always has been:
include "net/http"
I love Rust, but its DIY libraries can be quite a barrier to overcome when starting a project.In the future the answer may be go to <SITE-URL>, look up the HTTP server category and either read through a few one-line descriptions or just use the recommended package.
But: no matter how well-curated your site is, and no matter how quality those libraries are, and how well maintained, "batteries-included, in the stdlib, backed by a compatibility promise that the dev team is willing to uphold" carries some serious weight.
Considering that the Rust dev team is mostly volunteers, and many of the most important non-std-lib libraries in the Rust ecosystem are maintained by those same people I feel like there's less of a distinction than there is made out to be.
For me Axum should be the default recommendation because it's the least quirky option. It's also a very thin layer on top of hyper, and built by the tokio team, both of which have a very long track record.
Actix-web is a fine choice, but it's a bit of an odd duck in things like not being based on hyper, which makes it a little harder to integrate with the rest of the ecosystem.
Axum is very young (was announced <1y ago), it has good momentum, is based on hyper and built by the tokio team. On the other hand, it has few actual projects built with it, I couldn't find a guide for it like actix or even Rocket have (it has some documentation in its docsrs documentation, but it is pretty minimal). Crucial questions like how to handle configuration, integration with database, autoreload, are left to the examples at best.
I have no doubt that these things will come if the framework matures. I'm just questioning if it should be the default choice right now, as opposed to in a year.
(Btw, I would absolutely recommend against Rocket right now, even though API wise it clicked the best with me, until it gets more maintainers and development resumes)
You -CAN- go the route of using a well supported, well maintained library but I have seen some code rot on larger projects in the go code ecosystem for stuff that tried to include more batteries.
https://blog.cloudflare.com/the-complete-guide-to-golang-net...
But I spend probably 50% of my time dealing with production problems due to this in Java business applications.
Nearly every client library in the Spring ecosystem has the worst of all possible defaults, and all internet code examples are “look at how easy X is” without anything around making it real.
Devs chuck code out without HTTP connection pooling or pipelining requests, let alone reasonable timeouts. Hikari has an awful default DB pooling config, and on and on. Inevitably it gets reported as “latency” when a quick look at [APM tool] shows all the time is spent waiting on a connection, or a stack trace shows which library chucked the timeout.
And often devs just bump connection pools higher when they see a bottleneck and there’ll be 1000 idle connections wasting DB resources and not helping the problem (inefficient query, missing index, etc).
I complain because the defaults here are masochistic. But there does need to be a cultural change where using a client or server library assumes a tuning exercise. And yeah, good testing would be great too.
There are no 1-size-fits-all values. You have to look at the use case and the SLOs of the app and it’s up and downstream dependencies. You don’t want optimizing a config in isolation to break a larger system either.
Software development is hard. It requires understanding. No language or library can solve that for this stuff. I just wish all of these libraries’ docs called that out up front vs projecting plug and play. It’s all there, but buried in the godoc for you to have to know what you’re looking for. Others are worse.
This is especially the case with http libraries, since it’s usually building a client or server object/whatever correctly and not just a config tweak you can slap in at 2am.
And back to the original point, when there are 12 implementations that can mean having to relearn this activity for each one. That said, if I’m using Go, that usually means building something with net/http and handing it to the library to use instead of the default. Other languages without that base layer baked in don’t get that advantage.
Back to Java/Spring: usually Catalina vs netty/Reactor Core are 2 totally different worlds and often dev teams don’t know which they picked or that they switched when they generated a new project and pulled their code in from Spring Boot 1.5 to 2.x.
Which add +100 dependencies, many of them 0.x and constantly changing. I remember updates where I clicked through the docs of three different crates to decipher cryptic compiler errors. And don't get me started on getting started with Rust - Go is much easier to learn.
I would say that having Ken Thompson and Rob Pike's names attached was far more significant than Google. More than that, though, it was successful because it solved problems people had at the time. The solution space is much larger today, but at the time Go was quite unique in filling its niche.
Go was created at Google, and prominent among the language's goals, is that it serves their purposes.
Is this a joke? How the hell would I know which out dozens of library is complete, safe, well documented without significant research?
May, partly, sure...
I attribute Go's success due to the simplicity of the language. All successful languages tend to have one thing in common - they are extremely easy to read.
Forget about needing to decide on a web server library because there isn't one in the standard library. With C++ you have to decide which parts of the language itself you're going to use. Trying to learn is incredibly intimidating, because you look up how to do something simple and there's 17 unique answers with a million opinions on which is best, and which will lead you into a nightmare of unmaintainable code.
Go was built on the principle of being incredibly picky regarding what features are added to it. It's best assumed that any new feature that's proposed to be added will never be added, and until it goes through a heavy pitching process and gets hard-earned approval, it won't be there because the language has worked well enough without it up to now.
Having the formatting forced into the source code is genius too. Whether or not I agree with the prettiness of every formatting decision, I'm just happy it's there, because it's less decisions I have to make, and no matter whose code I'm reading, I know it'll be formatted in the same way as everyone else's.
The ability to have all the bells and whistles out of the box may be a lot of the appeal of Go, and is certainly a lot of the appeal of languages like Python. But it's not core to the ethos of C.
Though C++ and Java both probably have more container/algorithm libraries builtin than Go does.
people already complain constantly that the C and C++ standard libraries are bloated though
In C++ the standard library has several entire features that are regrettable and so just take up space (e.g. regex, yes regular expressions are useful, no the standard library implementation isn't what you want), as well as places where they kept thinking of new better ideas and now there's a trail of old busted stuff you shouldn't use in new projects.
So of course people are going to say that's bloated, it is.
C's standard library is rather spartan and includes some security issues. What C does have is a) interoperability-friendliness, b) a vast ecosystem.
People should learn lessons from C++'s success and understand how important it is to be able to easily consume C libraries.
I have yet to work at a Silicon Valley startup that used C# or F# though, despite it being a pretty solid choice in terms of "one language to rule them all" kind of thing like Kotlin / Java and (to maybe a lesser extent?) JavaScript.
I respect the tools and the language Microsoft has put out there, I'm not saying its objectively better or worse.
Especially F#, which is, to interject my opinion, an incredibly productive language on the same level as Go, and you get access to the rich .NET ecosystem out of the box.
I think C# and F# will get significantly more popular over the next 5 years.
And if this doesn't happen it will have been pure mismanagement of opportunity by MS.
As far as I'm aware Java and C# are still first/second by job listings in both the US and UK but in significantly less sexy enterprise settings. This might have changed since python became more widespread though.
I don't think Drew wants to compete with any of these languages. That's the point of this article.
Let's give this one some time before ruling it out right at the gate.
This way you won't need to give up on any of your existing, working, debugged C code.
I would say SPARK2014 or straight up Ada for life-critical software given the maturity and target markets. Rust is heading there, and there is some communication between Adacore and Ferrous Systems (Rust group)[1] taking place that gets me excited, but I will stick with SPARK2014 for now.
[1] https://blog.adacore.com/adacore-and-ferrous-systems-joining...
Honest question, what are the compelling ideas? I really don't mean that with any snark. I haven't read through everything on the site, but so far I see:
- Uses the `let var: type = value` syntax
- Infers `type` from the value when it can
- Has arrays and slices of arrays
- Uses `defer` for cleanup
- Doesn't have a garbage collector
- Imports using `use` (vs `#include`)
- Module scoping with `::`
- Has `match` (not sure about patterns/destructuring)
- It looks like `yield` is a return from sub-expressions
edit: some more...
- tagged union types
- non-nullable types (opt in)
- utf-8 strings
I asked the #hare IRC channel to add their own thoughts, will edit this comment with any answers:
> Simplicity.
> I think a strength is the plan for long term stability.
> It's simple and the compiler is tiny.
> First thing that comes to mind is error handling.
> The syntax seems simple to me, like c and go and unlike c++ and rust.
> I think a major strength is you can transfer your C expertise.
> You don't have to worry about your post-1.0 code breaking or becoming obselete from new language idioms.
> Clear communication from the hare dev team about what the project goals are.
I suspect "simple enough" is a threshold/constraint, but not really the goal.
If you have a formatting operation that produces an in-memory string, you cannot use it if the amount of text being produced is too large, or if you do not know if it is too large. But if it is not too large, you can then output the string to a buffered output file, although this is less efficient because it involves an extra copy and potentially dirties a lot more memory.
So these two ways of handling formatting are easily interconvertible when memory permits, but in some circumstances only the first one is applicable, and it is always more efficient.
Therefore, when reliability or efficiency is important, generating a formatted string should be implemented as a layer on top of sending formatted output to a stream, not vice versa.
There are cases where reliability and efficiency are not important, because your code is being used by end users and not as part of a library, and an error message is also an acceptable result of trying to run your program, and in those cases you should probably write your program in a language like Lua, Python, or JS.
It is probably true that if you are used to languages like those, a lot of design decisions that are necessary for reliability and efficiency will be counterintuitive to you.
fmt::println(x, y, z);
instead of (also untested) fmt::fprintf(os::stdout, "{}{}{}\n", x, y, z);
? I mean I agree that presumably anything you could do in the first form could be done in the second form, less conveniently as you say, but if you omit println from fmt then all the Golang programmers learning Hare will be surprised about the missing stair they expect as the Hare counterpart to fmt.Println. And I think omitting the terminating newline in your format string is a common enough bug that it's probably worthwhile to include a separate function that adds it implicitly, especially in the case of fmt::print, which doesn't have a format string to add it to.Even without Golang experience, if you're going to have an fprintf and a println, I'd look for the println in the module that has the fprintf in it.
Also not all streams are equal: stdio is heavier than file streams, which in turn are heavier than memory-backed streams (for example, stdio will probably need a built-in lock while string streams needn't). If you only need for example memory-backed streams you want to never see any code related to stdio, which might not be possible in some designs.
I don't remember if Golang "stdio" has built-in locks. Hare doesn't seem to have threads or locks, o that may be a non-issue.
Why is io::handle a separate type from io::stream? Is it just an efficiency hack to avoid an indirection through a function pointer for the common case where what you're writing to actually is a file? Can't io::handle eliminate the entanglement between buffered output and users of streams like the old io::println? I feel like there's something I'm not understanding here. (Is fd: int really the right way to handle a pointer to a structure representing how data is being transparently compressed into a zipfile, or a terminal emulator state that is being updated by writing bytes to it?)
io::handle is separate from io::stream because it needs to store either a stream or an io::file. io::file is necessary because it's required for many operating system constructs - a TCP socket is an io::file and cannot be an io::stream, for instance. Only io::files can be passed into syscalls like poll(2). However, stream is separately useful for building userspace I/O primitives, such as io::tee, cryptographic hashes and streams, and so on. So io::handle allows you to have an I/O object which is either a file descriptor (io::file) or a userspace stream (io::stream), but your code doesn't have to care about which it is.
I do understand why io::file needs to be separate from io::stream. (The io documentation introduction gives an explanation of what you're explaining above; I'd additionally offer the examples of a gzip stream, a UTF-8-decoded stream on an ISO-8859-1 text file, and maybe a stream that feeds into in-process terminal emulator logic.) I was asking why io::handle does. If you're writing code that takes an io::handle, generally the code cannot rely on the fact that io::file can be used for select() or ioctl() or getpeername(); if it needed to do that, you would have written it to take an io::file, not an io::handle. So, if your code is only going to invoke io::stream-like operations on the io::handle, it would be simpler if its argument were an io::stream instead of an io::handle.
But what if you want to give it an io::file? Well, it's easy enough to wrap an io::file in an io::stream that just invokes the appropriate rt operations, and in most languages the only per-call cost of doing that is that your function call is indirect (mov 12(%ebx), %ecx; call %ecx) rather than direct (call Zn#]io31337write). In fact, most code would be more* efficient that way, because right now if someone gives you an io::handle and you want to read from it, you're usually going to call io::read on it, adding an extra level of function call around the indirect call, because that's simpler than duplicating io::read's conditional call to rt::read in your own code. But dynamically most read and write calls will probably be on a bufio::bufstream (or some other io::stream), so io::read is just going to call .reader().
Does io::handle maybe exist only as an optimization for sendfile()?
Maybe it's too late for such changes, given the amount of existing Hare code, and this qualifies as bikeshedding, and if so, I apologize.
io::file exists for where it's needed to interface with the host system. io::stream is needed for userspace streams. io::handle is needed so that code which just wants to do I/O but doesn't care which of these two paradigms is in use can be useful for both.
This seems like an extremely rare case, which is itself a subset of the very rare case that you are formatting something that can be arbitrarily large. Oh, and the programmer must somehow have overlook this and not written it formatted output to output in reasonably-sized chunks.
Meanwhile, having back-and-forth between the formatter and I/O means the formatter must correctly handles all the I/O problems and corner cases: disk full, disconnect, transient failures, ... not even going into that for some of them, you might want the caller to decide the outcome, which is easy when you split formatting and I/O, but hard when the formatting performs the I/O.
I think this is a case that it may sounds superior to mix both, but it is actually buying next to nothing but both complicates matters and probably hides or ignores real issues.
It takes a minimum of twice as much memory to store the original data and its formatted stream. Some systems don't have much memory, and some systems work with large amounts of data. Some systems work with more data than fits in RAM, or more data than even fits on disk, or even infinite streams of data, from sensors or generated data. All of these use-cases must be accommodated by Hare.
That said, you can easily format into a dynamically allocated string in Hare if you so desire:
let s = fmt::asprintf("Hello, {}!", user);
defer free(s);
This has the obvious downside of requiring an additional memory allocation, which can also fail just as easily as I/O can, and must be freed after use. However, this function does not require I/O, so all of the I/O failure cases are ruled out. There are trade-offs between these two approaches. Hare's design is meant to let the user evaluate these trade-offs for their use-case and make an explicit, planned decision regarding their needs and potential failure modes.Incidentally this is one of the things that the limited form of laziness found in Python generators could help with, if Hare had it; you can think of an input stream as a lazy string. Unlike full laziness, generators have very predictable memory usage.
Is there something about Zig that rubs you the wrong way? Honestly, Hare almost seems like a Zig subset.
Edit: I respect your technical skills and sheer volume of output a lot so not trolling here, just trying to gain clarity after reading the language introduction.
Absolutely hilarious (and thanks for making this decision!)
Smart choice for packaging. Keep it simple should be a rule, not the exception.
That should be somewhere in summer of 2023, if we go by previous release dates.
C coders are defined by having seen a thousand languages go by and passed on all of them. C coders like C to the exclusion of all else, or they would have abandoned it long ago. Hare will not be picking up any substantial number of C coders.
Hare will not be picking up any C++ or Rust coders. It is a huge step down, offering literally none of what makes either language compelling for its users.
Likewise, Lisp and its offshoots. And Haskell, Erlang, MLs, APLs, Smalltalks, and Adas, all themselves niche.
Hare will not be picking up any Forth coders.
Zig is much more mature, and will maintain its lead. Nim is more mature, but will continue trailing Zig. Hare might chase after Zig alongside Nim.
The normal fate of any new language, absent The Miracle, is to fizzle. It is the certain fate of any language that brings nothing compelling to the table. Hare is exactly such a language.
A few people may continue using a fizzled language, indefinitely, like people maintaining their DVD collection. But there will be no reason for others to pay it any attention. The overwhelming bulk of the value in any language is network effects, and a fizzled language has none.
I think they DON'T want to compete with any language nor attract C/C++/Rust/Zig/Nim/Forth/<name_your_language_here> coders.
From my point of view, all languages which don't add anything new and improved are a drag on our collective computing experience.
I'd be happy if humanity had like three or four programming languages: Idris or some other better-Haskell (for guarantees), Lisp (for simplicity), Rust (for performance), and probably one or two other languages which are unique enough, Prolog or something, K?
I couldn't disagree more. The more people writing languages and implementing standard libraries the better.
Are these minor languages going to be used in production? An even smaller minority of them, sure.
For the majority of them it's a chance for developers to relearn data structures and algorithms and compilers. Real, hands-on practice that is hard to beat with any other method. I only got decent at algorithms and data structures by implementing languages and standard libraries.
I'd rather every developer built their own language.
If people weren't experimenting with weird esoteric languages, how would we have gotten any of the languages you list here? If I'm recalling correctly, K is descended from APL, a language you needed a special keyboard even to use. Rust synthesizes ideas from lots of small academic languages no one will ever ship software on.
I guess my main gripe is the duplication of effort: same thing with a slightly different spin, the time wasted learning different libraries doing fundamentally the same thing, etc.
Mind you i don't know, i like Rust - but i'm not a C-like programmer and don't want to be. But, nevertheless my opinion of Zig seems quite high. It seems Zig is the benchmark in this space.
It seems like a lot of people choose languages due to ecosystem constraints?
There's even old languages dug up for this purpose. Most pascal I have seen lately is malware samples.
That being said: automated malware analysis is of dubious efficacy and flexibility to begin with, so who knows.
Write any trivial program in vb6 and upload it to virus total. Red alert left and right. Even Windows Defender of all things goes off for anything a little more involved. Sometimes only a couple of days after you did the initial scan. Signing the binary helps, but is no silver bullet, and sometimes companies still can't be arsed. There's an endless thread in the last remaining forum about vb6 where people try to figure out what increases chances of detection, and it mostly reads like cargo culting.
I understand the network effects and 'community' benefits of large/non-fizzled languages, but I want to believe that a language that makes an individual programmer happier, more productive, better able to meet engineering goals, or whatever metric they seek to increase through choosing one language over another, is the language they should be using.
I remain hopeful for a heterogeneous approach to language usage in the future, where multiple engineers working on those applications can utilize a language which they determine is best for them. I think this could results in multiple languages which all compile to a well defined target language, via well specified semantics for the source language transformation. I know this is almost the idea behind the JVM or CLR, and is similar to many languages transpiling to C. I would like to see an environment cultivated to support this from the ground up and grow from there.
Thus, there are very powerful organizational forces pushing for a single implementation language. A practical exception to this is that there are plenty of programs coded in a systems language providing a scripting language (e.g. gdb in C++ with Python scripting, Emacs in C with Elisp, Vim in C with Vimscript) made tolerable by the scripting language being extra-easy, very widely known, or known to all users of the program, so familiar to any contributor.
But the need to learn another language, or two or three, just to contribute to a project will turn away people not able to spare that much attention. Businesses would need to pay for the time all the developers need to learn all the languages. And, languages will be at multiple levels of maturity and stability. What do you do when 10% of your system is coded in a now unmaintained language? When you want to port to a new target execution environment, does each language not ported there get to hold you hostage?
This is why languages with a wide range are attractive. C++ and Rust aim for this wide range, providing for both bit-twiddling optimization and system architecture organization. Supporting a wide range is a big burden for a language maintainer, so there will never be very many like that.
I doubt very much that there are many C coders who like the C language.
I had liked very much C around 1990, when I could use for the first time the Microsoft C compiler and the Borland Turbo C compiler.
Previously I did not have access to programming languages better than Cobol, Fortran, Basic or Pascal. In comparison with any of those, programming in C was much more enjoyable.
However, later, after better languages became available, I had no reason to continue to like C.
Despite that, I continue to program frequently in C, because there are still cases when it remains the best choice, for various reasons having nothing to do with the quality of the language, i.e. mainly for embedded computers or kernel drivers.
In any case, the C language will always remain on the list of languages important in the history of programming languages, independently of how many programmers happened to use C or to like it, because of a couple of valuable innovations: the "continue" statement and the distinct operators for the McCarthy "and" and "or" and for the bit string "and" and "or" (though it would have been better if single characters would have been used for McCarthy operators and double characters for the bit string operators).
(A few other innovations that are sometimes attributed to C had actually been introduced in the language B, the predecessor of C, or in the language BCPL, the predecessor of B, or in the language CPL, the predecessor of BCPL.)
(It probably is better on net, but) not as much as one might think, actually. It's a clear win in terms of Huffman coding, but there's also a general principle that small infix operators have higher precedence, and large ones lower precedence, like how multiplication in math notation uses "·", or more often nothing at all, while addition uses "+". (It also shows up in natural languages; consider "Tom and Dick and Harry as well as Alice and Bob".) It's (probably, usually) worth subverting for assignment, because "=" is used a lot, but I'm not sure if it's a good tradeoff for boolean operators, eg:
if(x == 3 & y == 5) foo(); // look's a bit too much like it's saying
if(x == (3&y) == 5) foo(); // ie (x==(3&y)) & ((3&y)==5), rather than
if((x==3) & (y==5)) foo();
Though, like "=", the initial misleading hueristic might wear off with sufficient exposure, and you can usually fudge it by adding spaces around the low-precedence operator.In my opinion this is one of the greatest mistakes of C, and which unfortunately has been inherited by too many other languages. Even Dennis Ritchie has admitted that this might have not been a good idea.
That wouldn't help "!=" and "<=", and "=" is used far too often (in typical code, hence the "(probably, usually)" above) to be multi-character.
"!=", "<=" and the like were single-character operators in Algol and in many other early programming languages. They have been replaced with 2-character operators only because IBM and most other important US computer manufacturers were not willing to support character sets with enough characters for covering the needs of mathematics and of other languages than English.
The target for the US character sets was only the text that may appear in commercial letters written in English. For any other uses, like programming languages, characters have been accepted only when they could occupy one of the few vacant places in the character map.
The dominance of the US-made computer hardware forced this ugly limitation upon the programming languages. So the programming languages had to use various commercial characters, e.g. !, @, #, $, %, instead of more appropriate traditional mathematical symbols.
Now, with Unicode and UTF-8, I consider the use of ASCII for source code as stupid.
In Unicode, also the ":=" of Algol exists as a single character.
Regardless of how operators are encoded, as single- or multiple-characters, a good text editor for source code allows one to change the correspondence between pressed keys and the inserted text.
Because assignment is used more frequently, even when using ASCII a text editor can be configured to insert ":=" when you press "=", and, for example, to insert "=" for ctrl-= and "!=" for alt-=, in order to minimize key presses.
For me no dice is being unsafe by default.
Thankfully in the context of C++ I could always fix, what from my point of view, are bad defaults regarding bounds checking.
C++ was attractive to me in 1993, because it offered me the safety, and modern language features I was used from Turbo Pascal and then some.
With just that, I'd say it's a win in my book. Not mentioning actual, useful strings.
I just pointed a couple of it's good sides, that make it worthwhile for myself to eventually learn.
It is based on a stable standard.
It is extremely portable and not in a target major platforms way - that is much better done in other languages - but in a being able to run on nearly every OS and hardware in existence. You can feasible port your C software to you weird hobby operating system.
It is simple enough that you could write your own compiler or at least something to bootstrap a proper one.
It is the Latin of programming languages. It is the common, cultural core we have. Nearly all bigger languages have some way to interact with C. If you want to write a library that can be easily used from any language, C is the way to go.
It is (relatively) fast too compile and creates binaries of minimal size that run at maximal performance. (Yes, you do make trade-offs in some of these aspects when using Rust or C++. Those trade-offs are worth it in many cases but let's don't pretend they don't exist. C serves as a performance gold standard for a reason.)
Yes, it is also a horrible language is many ways and should not be used for most projecs but the advantages can make all the difference for certain use cases. For projects like SQLite or Lua it was for sure the right choice to use C.
Just because you don't care about the downsides does not mean there are no there. First of all, which subset of C++ should be used? It is a huge language that only very few people can claim to master. So lot's of people wouldn't be able to understand or change the code anymore. That is quite the significant downside.
Not to mention all the other downsides mentioned in my other post, like being slower to compile, bigger binary and so on. Yes, those downsides might not matter to you and should not matter for many projects but they do exists.
C programmer often care more about building the best possible software not so much about having the best possible development experience while building.
That said, Rust has much better safety guarantees, so it DOES offer some significant upside that C can't.For more complex software it is is definitely a very good choice.
I do have experience using both C and Rust. Arguably, my C++ is very limited but things like C++ being a relatively large language are well established facts and quite uncontroversial.
It is true that some people resist using anything introduced since they first learned, with a few mired in C. But there is never any need to "agree" on that.
"Bigger binary" is scurrilous propaganda. Compilers nowadays use the same code generator for C and C++: say the same thing, get the same code. Furthermore, optimizers have to guess less at you are trying to do, in C++, so can do a better job. C++ code for the same task is often smaller.
"C programmers often care more about building the best possible software": this is just crude slander.
Finally: "Rust has much better safety guarantees": C++ offers much the same guarantees, where you choose to exercise them.
> Part of our work in developing Hare is laying the groundwork for a collaborative, productive, healthy community that people want to work in, ...
Maybe that's enough? It's okay to be a niche, even toy, language. The community is the "feature" that makes it compelling, eh? Like how Gemini doesn't compete with WWW, or indeed how Sr.ht is not exactly a competitor to GitHub, but still has its value.
In what sense? I mean, he's not entering contests with it? (Forgive me for being dense.)
> He just doesn't want people making fair comparisons.
I dunno, I feel like he's been pretty up front about where Hare is different (not to say inferior) to other languages. (I should mention that I'm generally pro-ddevault even though I don't agree with everything he's into. (E.g. I'm not on the Graph DB bandwagon (yet?).)
FWIW, I see Hare as a fun toy (a toy that can do real work through, to be sure.) As long as it and the ecosystem around it can attract people to work/play with it then it doesn't need to compete. (Except in the general sense of competing for time and attention with everything else.)
Those irrational people seem to have code that doesn't require rewrites/refactoring every few years. Switching to an entirely new ecosystem as a primary field? I leave it to the beta-testers, perhaps in 10 years Zig/Rust/Nim will be standardized languages with multiple compilers and big ecosystems supporting them, just like C and polished enough to be as easy as C to write.
I agree. And for that matter, see a lot of the same for Zig too. Hardcore C coders have learned to live with it or have incurable Stockholm syndrome and stay despite of the situation.
I believe it's really a matter of programmers who are newer (younger), casual, or part-time at it (like various people in IT or security) that are looking for more convenient alternatives. In that case, things are more wide open. They could pick Go, Odin, Vlang, Hare... just as easily as they could pick up Zig. There are many "better C" alternatives vying for position. It could shift so easily.
As for Nim, I think they have a certain level of guaranteed "lock-in" for being so Python-like and seen as an alternative to it. How far that will take them, remains to be seen. Same for Rust, they have "safety" to play on, and the backing of Mozilla that added wind to their sails.
> The normal fate of any new language, absent The Miracle, is to fizzle. It is the certain fate of any language that brings nothing compelling to the table.
True.
Except under proprietary operating systems. It's really not that difficult to compare Hare with C. C works everywhere, Hare doesn't. It's disingenuous to compare languages that aren't even close to the same portability level. C, Rust and Zig can be used to implement any imaginable piece of software, from desktop to server on almost any architecture and operating system. Hare is a niche language for Linux services, so I don't think anyone had any doubts Hare would not replace C.
Unlike LLVM, adding a new backend for Hare is a relatively straightforward effort: riscv64 was done by one person in a few months and is only 1,476 lines of code.
And again: the answer to the question posed by the blog post's title is "no".
That is likely just trolling, but is also specially obnoxious considering that Rust does prevent use-after-free bugs as much as Java does (i.e. a lot, but definitely not all).
However, I really do think for a new "systems language" nowadays, you do want to look at how major security holes occur in practice, and have a good story on how users should avoid them.
> Cryptography is a difficult, high-risk domain of programming. The life and well-being of your users may depend on your ability to implement cryptographic applications with due care. Please carefully read all of the documentation, double-check your work, and seek second opinions and independent review of your code. Our documentation and API design aims to prevent easy mistakes from being made, but it is no substitute for a good background in applied cryptography.
We have many safety features built into the language and the standard library is designed to be difficult to use incorrectly. I will address these concerns directly in a subsequent blog post covering the safety and security features of Hare.
The main problem is that some programmers view anything less than what Rust provides as morally unjustified.
That introduction is simply an appeal to "I can write safe correct C." with more words. Which obviously is not true.
No it doesn't. It has a bypassable compile-time verified lifetime system, not an infallible programming guarantee. It leaves it entirely up to the developers to write correct and safe applications and libraries, and merely provides (powerful) tools to help.
(I realize this context was in avoiding common security bugs which are usually less likely in memory-safe languages, but it's important to not overstate the benefits.)
"use-after-free", "data race", "memory corruption", "out-of-bounds write", "read from uninitialized memory", "execute arbitrary code"...
Everybody already knows Rust has unsafe blocks and C FFI. It is not invulnerable to those problems, Rust just makes it very clear where those problems may appear, and if you are smart, you will place most of your code outside of those regions.
Looks like Rust is much safer on practice than what I expected.
Ending up compromised by a problem in tokio, Pin semantics, actix or all the necessary ffi bindings is no different than, say, a C program being compromised by a vulnerability in OpenSSL or libcurl.
A very significant number of memory issues in C stemmed from issues in such single high-profile dependency, so one should not undermine the threat of a bit of unsafe code in the corner of a library.
Rust is better.
It's very much human nature to trace the line in the sand juuuuuust right behind one's heels though, depicting everyone behind as bad and everyone ahead as zealots.
Rust is definitely better, hands down, but insisting on thinking code that interacts with unsafe blocks can be "safe by default" is a dangerously wrong mindset which also makes unsafe blocks proliferate without the necessary caution as the problem seem "contained". Anyone remember the actix unsafe saga?
But even though a program with unsafe blocks (read: all rust programs) are by definition not memory safe - calling a language memory safe on current platforms can to some extend even be considered a misnomer - the assistance provided by rust by default certainly helps make such programs much safer.
That's very different from environments where all code must be correct no matter what. But the impact of bugs isn't what changes.
One is that a large fraction of these security issues are not real, potentially exploitable vulnerabilities, but merely the fact that it is possible to abuse an API to subvert Rust's safety guarantees. These are things would, by the standards of other programming languages, not be worth even reporting and be considered user error.
The other is that a surprisingly large fraction of these are not from regular unsafe Rust code, but from misunderstanding the guarantees a C library makes when creating bindings for it. This is to be expected, as fully understanding those as a library is pretty difficult.
A total of one memory safety issue reported for an entire ecosystem this year so far also seems pretty good.
All in all, I think these are both pretty promising signs that the safety guarantees Rust provides working as intended.
In an ideal la-la land world, there exists an abstract interpreter that can consume safe Rust code and does not enforce any contracts upon the programmer (and hence will never have any undefined behavior). However, real world hardware definitely has contracts which developers have to obey (manually! because of the constraints of actual semiconductor physics! no compiler hand-holding here!). And on top of that all major OSes (Windows, MacOS, Linux) are written in C (so you need unsafe FFI to interact with the OS).
Of course you're right that that isn't true as stated, but I think it's interesting to try to situate this point along a continuum of other similar points:
1. C with Valgrind and sanitizers isn't always memory safe, because those tools are limited by test coverage.
2. Python isn't always memory safe, because many libraries including the standard library call into C code.
3. Pure Python that doesn't call into any C code isn't always memory safe, because the interpreter might have bugs.
4. Provably correct Ada with a provably correct compiler isn't always memory safe, because the proof checker, the compile-time hardware, or the runtime hardware might have bugs.
I think we all agree that there are important differences between 1 and 4, beyond the simple fact that the defects get less common as you go down the list. Here are some things that stand out to me:
- In cases #2 and below, the application code isn't "at fault" for any memory unsafety that comes up, and whatever code is at fault can be fixed to restore memory safety without changing the application.
- In case #1, there's no clear boundary in any sense between "safe code" (which we know isn't at fault for memory unsafety) and "unsafe code" (which might be at fault). There may be a distinction between code that's well covered by tests and code that isn't, for example, but it's often not easy to tell which is which. In case #2 and below, the boundary is pretty clear.
- In case #1, the amount of "unsafe code" in an application probably grows linearly with the size of the application, or maybe we just consider the whole application unsafe. But in cases #3 and #4, unsafe code is confined to low-level dependencies that get a lot of "battle testing" compared to how much code is in them. Case #2 is kind of a gray area, and we need to look at what dependencies the application is using.
So where should we situate Rust in that continuum? Is being able to write unsafe Rust code more or less risky than being able to call into C? It's certainly a lot more convenient to write an `unsafe` block than to cross the FFI barrier, and maybe that convenience is dangerous. On the other hand (contrary to some common misconceptions), unsafe Rust still benefits a lot from the borrow checker and other safety features, and it might end up having a lower rate of defects for that reason. Maybe it's too early to tell?
But anyway yes, I totally agree that the Rust community has a hard time getting the messaging right about how safe code and unsafe code work. But even though this discussion is really important to Rust, I'm not sure it's a "Rust problem" per se. I think it's actually quite difficult to talk clearly and correctly and precisely about memory safety in general.
In practice, this makes vulnerabilities in eg. argument parsers (like the recent "baron samedit" vulnerability in sudo) incredibly unlikely.
Safe Rust cannot ever cause undefined behavior, but Unsafe Rust can. The ultimate merit of Rust is that when you suspect any undefined behavior you only need to check the unsafe part, which is a much smaller percentage of your codebase (as opposed to C/C++ where you need to check the entirety of your code)
Whatever that means lol
You mean the borrow checker? People are working on formally proving that, and have already done so for large subsets of the language.
In other words, incorrect code in "unsafe Rust" can cause safety issues that only appear when you use it in a certain way from "safe Rust".
It is possible for two people who both take security seriously to come away with different take-aways. Once some CVEs are found in Hare you might have some fuel for your argument, but until then it's just speculation.
These are all GC'd languages that run bytecode on an abstract virtual machine. They avoid use-after-free by just not-freeing, if necessary at the cost of leaking unbounded amounts of memory.
This doesn't invalidate the point that borrowing isn't the only way to solve this problem, but there are definitely classes of these bugs for which Rust's borrow checker is the only known production-ready solution that still has manual, deterministic memory management.
Also I was wrong before and you were out of line. Matthew wasn't trolling, he never said you should be held criminally liable. You just made that criminal part up for no reason. Anyone should be held socially liable and shamed if their project has bad security and they refuse to fix it after they knew about it. I think you would even agree with that.
> Part of our work in developing Hare is laying the groundwork for a collaborative, productive, healthy community that people want to work in
The goal to have a healthy community is laudable but it comes from the top and hitting out at others doesn't set a good foundation. I'd recommend rising above by responding to critique without emotion.
That's solely my point.
> Look if you can't understand that this is a thing that will happen in the real world and that people will potentially suffer as a result you shouldn't be writing a crypto library.
Which is still far from suggesting someone should be prosecuted.
But it confirms what I was thinking, going from "liable" to "criminally prosecuted" is a pretty big stretch imo.
Reading the original comments about being "liable", it felt like another way of saying "there are consequences to your decisions, and as the author you bear some responsibility of what you put out there", which imo is pretty far from how the author of this blog post described it, hence me calling "a pretty big stretch"
None of what you said is even true, all of those patterns are trivial, except "back-references" which are very slightly non-trivial.
And none of it is relevant. We don't need another memory unsafe language. It's fine if it's a toy, but this obviously isn't. It causes real harm.
They all rely on having a mutable member reference to affect the outside world, which the borrow checker rejects. You can try to make it happen with a generic lifetime parameter for your structs, but you end up making invisible the thing you're pointing at, making it useless.
One can use unsafe to have shared mutability, or sacrifice speed with Rc/RefCell (increments/decrements) or Cell (copying, especially bad when copying Vecs which causes heap allocations).
Basic observers aren't possible. The closest thing we can get is some sort of modified observer-like substance that returns a command, which requires a lot of wiring and incidental complexity. [0] [1]
Dependency injection (the pattern, not the framework) isn't viable for the same reasons as observers: you can't have a mutable reference field without causing a lot of headaches elsewhere.
RAII isn't possible without sacrificing speed or safety. Most usages we see are backed by unsafe (FFI) or RefCell. We can't have multiple objects whose drop() affects the outside world, because we can't have multiple extant &mut references, and we can't just pass them in via parameters (because drop takes none).
Back references aren't possible because of the circular problem (having a mutable reference to your owner means nobody else can read it).
I'm not advocating for Hare, I'm just addressing the "I just don't see the excuse" remark, which can be ignorant of the costs of the borrow checker. The borrow checker is a great step forward, but not always a good tradeoff. An architect needs to be aware of these sacrifices before going all-in on a paradigm.
[0] https://stackoverflow.com/questions/37572734/how-can-i-imple...
[1] https://www.reddit.com/r/rust/comments/pwqju6/is_there_an_un...
idk what tot ell you
Cell is meant to be applied at the "leaves" of a type where individual assignments take place. When used this way, there is no additional copying relative to the straightforward C version. (Bringing up Vec and heap allocations here is also complete nonsense; Cell has nothing to do with Clone.)
Once you wrap your head around that, all your shared mutability examples translate over to Rust trivially. All the "sacrifices" in speed you are imagining are relative to `restrict`/`&mut T`, not the usual baseline of a simple mutable object.
To see it in action: Have a Database object, and try to have multiple Transaction objects that might commit something to it, in their drop().
It's unfortunately not possible, because they can't all have a &mut Database as struct fields.
We can sacrifice speed (by using Cell's copying or Rc's counting) or safety (by using unsafe). Most RAII we see uses unsafe FFI under the hood, which is why it was so surprising to me.
The very specific API design that you've described is not possible in Rust, but it is strange to equate this with the entirety of RAII. In any case, there are many alternative APIs (some with no sacrifice in speed or safety!) that are perfectly possible in Rust.
You need shared references for your DB, implying you need interior mutability. This is how Statements are implemented in real-world rust database drivers such as rusqlite (any operation on a db is done through a shared reference). The fact that a very real package is doing it proves that the pattern you're talking about is, in fact, possible.
However, achieving something like what you want is still more than possible in Rust. You can do this with the pattern of 'interior mutability', which in its simplest form is just a Mutex. This allows upgrading a shared reference to an exclusive reference, so that you can safely mutate an object while upholding the expectations that a mutable reference is exclusive, and a non-mutable reference does not change from under your feet.
Of course, for a database, you will probably want a more advanced implementation of interior mutability, so that you can commit multiple transactions at the same time. (Or not, it seems to work quite well for SQLite.)
Why does anyone need an excuse to build anything? He’s doing this in his free time. He doesn’t need the internet’s permission.
So let's summarize:
1. Simplicity and readability - C, Go
2. Tiny language - C, Go
3. Modularity - Go, Rust
4. Defer statement - Go
5. Metaprogramming (generics, compile time, macros) - lots of inspiration and some really fresh ideas like Zig and Jai, Go interfaces, and Rust traits look nice
6. Strong type system - Go, Rust
7. Manual memory management, pointers - C
8. No OPP in terms of C++, Java
9. No references, just pointers
10. Syntax - Go, Rust
11. Zero cost abstraction and as much as possible minimal runtime - C, Rust
Mostly their look like Rust with "defer" but without borrow checker, move semantics, references, RAII, and lifetime annotation.
Mb this is what we really need? :)
Rust is inspired by ML family languages and heavily leans on it's type system, while Go is very simplistic in comparison. (This has improved somewhat with the recent addition of generics)
In Go the answer is that strings are just some bytes, and filenames are just some bytes and so this naturally just works.
In Rust the answer is that strings are AsRef<Path> and you can open a Path, so when you call open the compiler gives it a Path even though that isn't what you actually had (you can't mutate the Path via this reference, so we know open doesn't change it).
The difference becomes more stark if we go the other way, starting from a list of files in the current directory and writing a JSON file.
In Go if you get a list of all the filenames in the current directory, it's a list of strings, and the fact that those aren't actually text is your problem, you will need to explicitly take care of this or you can't emit valid JSON.
In Rust, you get Paths, and you're going to need to explicitly ask for the strings to make JSON, at which point you have to decide what you want to do if the Path isn't just text, you're obliged to decide, even if it's just panic (ie abort the program).
You'll still emit valid json. The encoding/json doc says:
> String values encode as JSON strings coerced to valid UTF-8, replacing invalid bytes with the Unicode replacement rune.
I've had situations where I am obliged to write output to XML, and clients are like, "You can't lose this weird data in our text". That's not me, that's XML, in XML 1.0 most of the ASCII Control Characters are banned (not like "You must escape this character" they are banned if you write them as text, escaped or not, that's not a valid XML file and some parsers won't read it). You can either admit you want binary data maybe wrapped as Base64 inside the XML or you can accept that in XML those characters are toast. I would turn all the banned characters into U+FFFD in XML.
JSON doesn't have that defect, but it still can't magically make non-text into text, and some filenames are not text. So this feels fair enough. Chances are if your JSON format is so capable that you can write "Here's a Non-text filename" and have that work almost all the people parsing it ignore that case and you didn't really improve interoperability at all.
- Defer is nice for cleanup, but I think destructors are the gold standard there. They're even simpler to reason about, you can't forget to invoke them, and combined with move semantics they automate away a really wide range of cleanup scenarios. Another interesting difference is that adding destructor-based cleanup to an existing type that didn't previously have a destructor is often a backwards-compatible change. Lastly, GC'd languages like Go usually need to include some sort of finalizer mechanism in addition to the defer syntax, and finalizers are surprisingly complicated.
- I don't think C should get full points for zero cost abstractions. There's a lot of pressure to use void*'s or intrusive structures in place of true generic containers like std::vector/Vec, and that comes with runtime overhead in practice.
> Hare is a systems programming language designed to be simple, stable, and robust. Hare uses a static type system, manual memory management, and a minimal runtime. It is well-suited to writing operating systems, system tools, compilers, networking software, and other low-level, high performance tasks.
I would be happy to clarify further if you're still unsure of what kind of programs are well-served by being written in Hare.
https://harelang.org/blog/2021-02-09-hare-advances-on-c/
And if you have any specific questions that you want to frame in the context of another language, I will do my best to answer them.
I’m definitely not looking for bullet point comparisons.
Rather, I’m looking for an example of implementing some concrete functionality in both Hare and — say — C, which highlights the advantages of Hare over C in implementing this specific functionality.
In the case of Hare, it doesn't seem to be good if you want to be able to have anything running on Windows/macOS, as one stated goal is "Hare does not, and will not, support any proprietary operating systems" according to https://harelang.org/platforms/, which makes it very unattractive for me at least, as I move across three platforms daily.
JavaScript only targets the browser. Shaders usually targets one graphic runtime. Maybe there is a space for a language that just targets a few OSes, or even just one? Remember that both C# and Swift initially just targeted one platform, so maybe it does makes sense to now have one OSS language for OSS nerds.
Or just regular old GNU/Linux like 70% of the Web. But either Android installs or FreeRTOS installs easily outnumber those.
Zig, though, does very clearly lay out its pitch right on the homepage, and although it's maybe not a table of checkmarks, it's a series of pretty clear shots-across-the-bow at other languages, in particular C and C++.
Rust similarly has a "Why Rust" block above the fold on its homepage; it's not quite as terse as the Zig one, but it's clearly that same executive-level pitch.
Hare's homepage has: "Hare is a systems programming language designed to be simple, stable, and robust. Hare uses a static type system, manual memory management, and a minimal runtime. It is well-suited to writing operating systems, system tools, compilers, networking software, and other low-level, high performance tasks."
Maybe there's a case to be made here that these bald assertions are no different than what Zig and Rust claim about themselves. But I also think it's reasonable to have different expectations around this for a brand new project vs ones with years of track record and existing mindshare.
https://sr.ht/~vladh/hare-project-library/
I hope that helps.
It seems to me that these crusaders (and I've seen a few) think that because you shouldn't build a big bridge out of wood, you shouldn't build anything out of wood. Is the thinking more sophisticated than that? Honest question (and I code in Rust).
It should be criminal to build anything out of wood. Heard of forest fires huh? Guess what, they are made of wood!
---
Sorry, I couldn't resist.
Our wood workers have a track record at that.
It's one thing laughing at wooden bridges. The other thing is we already have charred landscape full of them.
(A "true believer" for my point here isn't someone for whom Rust is their favorite language or their generic first choice; it's someone who gets angry if someone else doesn't choose Rust for some task, and especially gets publicly angry.)
I bet for a lot of the True Believers, they learned some language that isn't very good, like C, or C++-as-taught-by-schools, (which is a very bad language, much worse than C++ as a whole!), or Javascript at its worst, and then encountered Rust. Hey, I get it, that would be a pretty big leap! But you've got a path dependency in your opinions there.
I'd encourage any such person to broaden their horizons a bit. It's OK. Rust really is a pretty good language and you probably won't change your opinion of it much. Plenty of people I know and respect who do know many languages still have Rust as their favorite and general default language. But it is not the only good language in the world, and other languages do offer things Rust does not. Consider trying out the Erlang environment (either via Erlang or Elixir), or Haskell, or Lisp.
Isn't the whole point that Rust is a replacement for C/C++, specifically the whole "close to hardware" and (basically) zero-cost abstractions? If you can afford things like a GC or a VM there are way better languages, that's for sure.
Then (now you're about a year in), someone will tell you, you poor thing, you've been abused by being taught that way! Use modern C++ and the STL and never use a raw pointer again. Drop that OOP stuff, learn about lambdas and functional C++ instead. You then read flame war threads about the proper way to do things, which you finally learn to ignore as they're just people with inflated egos throwing things at each other online.
Finally you understand: people write firmware in C because it's about as low-level as you can get without going to assembly, and people write big projects and games in C++ because of all the libraries and the STL and good performance speed-wise, and then there are people who've abandoned C & C++ for Rust because of the memory leak issues and perhaps the convenient build system, and you never ever again write the kind of code you wrote for your assignments in your C++-as-taught-by-schools courses.
Finally, you take a few Python courses and marvel at how much easier it is to code in Python, but you feel a bit better off than those who learned to program in Python, because, you at least know what pointers are. Then someone tells you, hey, learn some Java too, it's easy to get a boring corporate job if you know Java. Anyway, that's what schools are teaching right now in their core intro CS curriculum, more or less.
Your description accurate but a little off because most students at least in America learn Java first as part of their AP Computer Science course, and a lot of colleges use Java as an intro language for this very reason.
However, there is definitely a subset of the community that is so bought into the idea that memory safety is an Absolute Good that any new developments, projects, or languages that don’t make it a priority are a priori bad.
I am a huge fan of rust, but we should let the language stand on its own merits and not assume that it is the only valid choice. Just because I and/or my company prioritize the features rust gives us doesn’t mean it’s the perfect solution for every problem.
That said, I do feel like this view that rust is the only valid language is a minority one in the community. It just is a bit loud at times.
Software engineering is more than writing a program, there are economic factors at play too.
That being said, I also wish my browser was written in Rust (and my OS too!).
I think the way it usually works is a less reasonable one would make outrageous statement, later on some one more reasonable sounding would put a context or nuance around it.
So a single crusader maybe called outright troll or whatever but if a few work together they can be much effective for evangelism.
Existing code and existing engineers are a massive barrier-to-entry.
One trap engineers fall into is we tend to view exaggerate the importance of certain problems. Verbosity in Java is a big one. IDEs fill it in for you. It doesn't slow you down. It's a complete non-issue.
Zig is interesting. I don't know a ton about it. My sense is that Zig is to C what TypeScript is to JavaScript. I mean Zig isn't transpiled into C but the point is that it seems to be very closely related so transition should be fairly easy.
Rust most closely competes with C++ (IMHO) but it does something really interesting that C++ just can't do: it tackles ownership and memory safety at compile-time. Yes, C++ has smart pointers but these incur a runtime cost. C++'s features, history and (dare I say it?) baggage mean C++ can't do the same thing.
I personally consider this to be an increasingly important issue so Rust has a definite niche. But will it displace C++? The odds aren't in its favor. But it will certainly be viable.
C is the funniest one though. Asking "Will X replace C?" is a bit like "Will [search startup] replace Google?" The startup landscape is littered with the ocrpses of Google-killers. Likewise the language landscape is littered with the corpses of C-killers. So my money is on "no".
I think a lot of C programmers dislike some parts of the language - something that's true of any language - but they like writing C code the way they have for the last X years. If they're willing to give up the preprocessor, they can keep using C. There's no need to replace C. D has support for all the platforms of GCC and LLVM. That's obviously not as many as C, but it's a lot.
It most definitely does! Perhaps not when writing code. But it does take longer to read/scan verbose code. I'd also argue many kinds of refactoring are made slower by verbosity.
This might be referring to comments elsewhere, but I thought there was a pretty thought-provoking debate about safety tradeoffs in the Hare intro thread.[1]
Which I'd summarize as: let's say we now know how to prevent, say, 70 out of 100 security bugs in C codebases, without performance compromise, by statically ruling out things like buffer overflows and use-after-free; and we also have good evidence that bugs your language ecosystem is bad at detecting are hard to backport detection for. Is it a good idea to make a language that prevents _most_ of those 70 mistakes, but not all that we know how to prevent, in exchange for being simpler, and therefore reducing the other 30 mistakes and getting more software done that helps people? Or would it be better to avoid investing in or relying on new languages with that tradeoff for infrastructure code, and focus on seeing how simple a language can be that prevent all 70?
Which isn't a logic question, but an engineering question: does the mostly-safe language prevent 65 out of 70 memory bugs, or 30 out of 70? Does it let you get twice as much done as the safer language, or 10% more done? Does it result in fewer logic bugs than the more complex language, or the same number?
I don't know, but I'm interested, because I want the next billion lines of code that affect me to do useful stuff and not break. "My goal is not to force anyone who doesn’t like Hare to use it" isn't really an option; I'll be impacted by all the code people write in every language. So: I'm happy to see people make new things that test a new point in the design space! But I'm _also_ happy to see other people say, wait, before I end up with a ton of this code tucked into the lower levels of my machine and the other hundred billion machines wired up to it, what mix of features would convince me that "less safe than we know how to make new languages" is still safe enough for a new language in this case?
I think I speak for a majority of the Rust community when I say that we're embarrassed by this sort of behavior and we desperately wish that folks would stop it.
[1] just for reference, 0.1 was announced 2012, 10 years ago, 1.0 was announced 2015, 7 years ago.
Then add configuration options for conditional compilation, and a build system to define them properly.
Then use the configuration options of your dependencies in your build system.
Finally, repeat this process for every project and make sure you use the same syntax/api for your build system so you don't have to relearn everything everytime.
cc main.c is as easy as it gets when you rewrite everything from scratch.
cargo is as easy as it gets when you want an ecosystem.
Still, yes it's a bit more complicated. But can we not pretend that the non-existent toolchain in C/C++ is easier in 99% of use cases?
[0] - https://medium.com/dwelo-r-d/using-c-libraries-in-rust-13961...
Once you solve this problem, C++ & CMake are of similar complexity for C++ dependencies.
Otherwise to compare C++ toolchains, if it's distributed as a .a file, just adding -l/path/to/libclosed.a works for C++, can't get more simple than that. Or one line in the CMake file with basically the same thing.
RUSTFLAGS="--extern closedlib=path/to/libclosedlib.rlib"Most software have dependencies that should be managed, linked in, figure the header file path of etc. Most software consists of several files which you don't want to re-build every time you change just 1 file (unless you change compiler flags etc.). It's a very tiny step from your example until the C or C++ toolchain becomes far from easy.
For packaging, you want your dependencies to be packaged as well, with build system using system-wide versions instead of pulling things from Cargo, with proper dynamic linking. There are several tools that help you manage that for Rust, Debian has some packaging helpers too, but it's pain nevertheless once you have to, say, support multiple crate versions in your code to deal with stable distros. Some of these troubles come more from the ecosystem than the tooling itself.
To some extent, I see that more as "Distros have ossified around the workflows and tooling that is essentially 'a third-party package manager like Conan for C++, but for C'".
How do you deal with the same monomorphization problems in C++ libraries? See https://blogs.gentoo.org/mgorny/2012/08/20/the-impact-of-cxx...
How do you deal with vendored single-header libraries and bespoke implementations brought about by C's lack of a cross-distro, cross-platform dependency handling story? You generally don't.
https://wiki.alopex.li/LetsBeRealAboutDependencies#gotta-go-...
As that article points out, Rust's approach to package management allows things to be shared between projects which, in practice, don't get shared between C projects.
With Crates.io not allowing existing crate versions to have their contents modified and Cargo.lock providing a SHA256-verified record of the exact dependencies your project used, you've got a record of what build dependency versions went into your package, which can be used by tools like `cargo audit`.
Yes, it means that you need tooling to automate rebuilding the binaries whenever they're statically linked but, as Michał Górny pointed out in 2012, that's already necessary with libraries that use C++ templates and, as was said in "Let's Be Real About Dependencies", "On the flip side, I wonder how many times vlc’s XML parser has been fuzzed?"
While it's slow going, discussion and an experimental prototype do exist for embedding full dependency versioning information inside Rust binaries to further help that approach: https://github.com/rust-lang/rfcs/pull/2801
How do you mean? Where?
Cargo is very opinionated, forcing upon you certain directory structures and even a default VCS. If you have a large project, cargo makes you jump through hoops. Building multiple binaries and libraries from one project is a pain, and has to be done exactly as cargo wants.
In terms of being able to "just get started", I think `gcc app.c` is way easier than using cargo. But that's not really important. You're not going to replace a million line C project with Rust just because "getting started" was a few seconds faster/slower.
No, it expects source code in `src` like most C/C++ projects anyway. Then it's `lib.rs` or `main.rs`. The opinionated directory structure (`pkg.rs` or `pkg/mod.rs`) is rustc not cargo.
> even a default VCS
Because it's the one used by most projects. Anyway, nothing prevents you from using mercurial, svn, ...
> Building multiple binaries and libraries from one project is a pain
Have you heard of Cargo workspaces? Because I've had no troubles with it.
> I think `gcc app.c` is way easier than using cargo
Agree to disagree. Because really soon, you'll want to manage dependencies.
In C/C++ there's no expectation of the code being anywhere.
> Because it's the [VCS] used by most projects.
I don't see how a VCS being popular is a reason to require it. It's not even "essentially all projects", just "most projects".
That's not what I said. I said that most project do it this way anyway, regardless of any expectations. So who cares?
> I don't see how a VCS being popular is a reason to require it.
It is not required.
Neither do the authors of cargo:
`cargo new --vcs none`
C & Rust come from different eras of easy-getting-started, IMO:
C - I just want to get shit done, I'm not taking on any dependencies, I'll just write it myself and sure it won't be the best but it'll do what I need and keep it simple;
Rust - I just want to get shit done, surely there's a lib for this and that, I can just glue them together.
So with Rust that's no harder to do than doing anything at all; with C it's quite a bit harder but also a bit easier if you don't.
Agreed that's not going to hold any weight in deliberating switching a million line project, but I do think it matters what hobbyists/spare-timers are using, what people want to work with, etc. Slowly.
(E.g. my day job is mainly python; if we needed something highly performant or embedded or whatever I'd be way more comfortable in Rust than ropey university-C.)
https://doc.rust-lang.org/cargo/reference/cargo-targets.html...
Open visual studio (or Qt) 'File' menu, 'New C/C++ project'. Press F7 to compile.
No command line, no cargo build, no cargo run, no cargo.toml. Just press the green "play" button and your full graphic application shows up.
Embedded with Arduino C++: 'file' menu, then 'new sketch', click the arrow to compile and download to the board.
On Windows: download shared from here(1), unzip anywhere. Right click on the project, select "Project settings" (IIRC) then point to .libs you want to use (or add them all), add include path (ffmpeg/include). Done.
On linux (Qt): install with apt-get. Point to libs, add include path (in the .pro file).
The only complication is that you have to wrap the #includes with extern "C" {} if you are on C++.
I did it for both platforms the other day and it works. Super easy.
Cross-compile: on Qt you do it through packs you select at install stage. Sometimes it requires the runtime to be installed (like for Android) but it's not complicated.
On VS, I don't know today. I remember for Windows CE you had to install an SDK, and compilation/debug was out-of-the-box (F7 to compile, F5 to debug in-target). And I used Platform Builder for years, and I cross-compiled the entire OS from the menu.
On Arduino: select your board from the menu. Also, installing libraries is done through a menu. Pulled from online resources automatically.
But I guess I will not convince you. You'll come up with another challenge.
I also remember that I took the "hard" path on Linux and rebuilt ffmpeg, but it was like "git clone", "./configure", "make", "sudo make install". I had to use the command line, but nobody died (being a huge C project and all that).
But we were talking IDEs vs command line and "actually understand your development tool chain in C as well as C++". Of course that niche, worst-case project are going to require more complicated steps. The thing is that the command line and clunkyness is being forced onto the other 99% of the cases too, for simpler projects and dumber persons like myself.
I consider them to be the best case because everyone wants them so there has been some improvement down the road. I think Cyph0n pick a wrong example for that reason.
> I also remember that I took the "hard" path on Linux and rebuilt ffmpeg, but it was like "git clone", "./configure", "make", "sudo make install". I had to use the command line, but nobody died (being a huge C project and all that).
That is a happy case for the reason I've said before. In my experience many libraries do not build that cleanly, it is a routine challenge to determine which flags I have to put to configure or which library I have to install (that might not exist in my distro so I may have to build them as well). Also `sudo make install` alters the global environment, which is never a great idea in Linux distros; so you have to either make sure to run `./configure --prefix=$HOME` or likes, or just run `make` and pick necessary bits out of the build directories yourself. The latter is painful, but the former risks two competing versions of the same library in the global environment.
Honestly though this experience greatly depends on tasks, and you may have barely hit those worse cases in your life. The Windows SDK and Android SDK you've initially cited are two examples where almost everything is available for you and you don't need as many libraries to continue on.
[dependencies]
# Safe Rust wrapper
ffmpeg-next = "5.0.3"
or [dependencies]
# Direct FFI bindings
ffmpeg-next-sys = "5.0.3"
Note that libav will be built from source, but it will be cached for subsequent builds of your project.And thanks to the build script maintained by the crate (library) owner, this process should work exactly the same across major OSes and architectures transparently.
Edit: Here is the relevant part of the build script that actually configures and builds libav based on Cargo feature flags and configured rustc OS + arch: https://github.com/zmwangx/rust-ffmpeg-sys/blob/499fca3630fb...
https://github.com/zmwangx/rust-ffmpeg/wiki/Notes-on-buildin...
And "Install FFmpeg (complete with headers) through any means, e.g. downloading a pre-built "full_build-shared".
Come on...
As for libav itself, if prebuilt libraries are not present, the build script will fetch the source and build it as a static lib.
0. Export/upload my source
1. Import/download my remote source
2. Compile my source for different targets
3. Cleanup my source and my compiled source
5. Do all the above for different versions/locations of my source
6. Do all the above for source not my own
7. Do all the above with a simple command and or a simple key->value in a config file
Then I need to do all the above with
8. using a terminal on my personal computer
9. on a remote server using ssh'ed terminal
10. in a script automation file like a docker file or maybe a vim script that setups my dev environment
11. Have 0 - 10 be so easy to do, basically a simple command or two, that I don't have to spend days figuring out tooling FOR EACH TASK and googling mystical error messages for solutions on obscure forums posted 2009.
I stayed with Rust because it has good tooling. Because it's the one thing that hasn't driven me insane at one point or another. I wanted to program in Haskell and c, but I am so sick of the terrible tooling and the terrible conventions of the communities (something Go at least gets right) that I instead program in Rust, a language I enjoy less for it's merit as a language.
I haven't mentioned conditional compilation.
I haven't mentioned respecting the configuration options of dependencies.
The tooling has to at least do all that using the same syntax for commands and configuration. It has to be predictable enough that I can almost figure it out myself. No black magic.
I work mostly in IDEs and won't use the clunky command line if I have an alternative. After all, it's the year 2022. Why working like it's 1991? I don't want to spoil you the end of the movie, but I have this hunch that graphical user interfaces are the future.
And I don't see what you can't do with VS or Qt. Maybe setting up your toolchain with a script or using terminals (which I don't know what have to do with programming IDEs).
I do most of the above with fricking KEIL uVision for a myriad of microcontrollers!
I compile and edit whole Android OS with VSCode and the integrated SSH terminal! (I hate to use the command line, though!).
Cargo add pkgname on my computer.
Cargo add pkgname on a ssh session.
Cargo add pkgname in a docker script.
Wow the same process for doing it on my dev pc is the same everywhere else! Amazing. What do you do to automate deployment? ssh in and install your whole graphical tool then write a mouse macro to click the compile button?
You are taking a vacation with a U-Haul truck filled with your furniture. I am taking a vacation with a backpack.
There is no need for ssh’ing or recompiling.
For embedded I just give a bin file with everything in it. Use your favorite flashing tool.
For the rest, I can make an installer right from the ide. You know, the kind of 3 click installers the whole world is used to in 2022. An installer I can give grandma. Huge success from the 90’s.
Or maybe redistribute my program with a mobile app market. I can do that from my ide too!
Or maybe I’ll just handle a zip file with everything in it.
I’m afraid you can’t see it’s YOU who are in a niche, and not the other way around.
Just email me a binary to flash with a usb stick? Here's a link to download a zip file, just drag and drop into file explorer. Totally scalable stuff. But then again you just make calculators for the android app store, so I guess you're fine.
Dont make the mistake of thinking you are better than others, otherwise you’ll keep feeding the general feeling of Rust cultism and you’ll end up programming in niche languages from the command line, 1991 style.
And you mentioned "boomers" so I know exactly with who I am talking to.
My ego walks away unharmed from this conversation.
I believe you have a capable need for the tools you choose to use. I wish I could have sympathized, but the snark you engaged with set the wrong tone.
Because the code written in HTML and Javascript isn't going to be rewritten in Rust another language, probably ever.
My main motivation for using Zig is that it's rapidly becoming a better C toolchain that GCC or Clang. So I'm extremely motivated to compile all my C code with Zig when it becomes possible. After that it's only natural to write more of my code in Zig.
So, my hypothesis is, a language that could replace C has to have a better C compiler built-in than existing C compilers.
Another side of this, is that it has to be extremely easy to use C from the new language and vice-versa. Something I also think Zig does mostly right.
Perl has Inline::C that allows snippets of C code to be placed directly into Perl code like many C and Pascal compilers allow inline assembly. That's for interoperation and an occasional optimization of course, since Perl is in no way a C replacement for much of what's done in C. Do you foresee a replacement language having such inline sections, being a superset language like C++ or Objective C, or being able to just handle separate files/modules in separate languages?
In the case of Rust, at least they made safety a thing to hang their hat on. As an competitor, how do you "out safety" Rust?
Golang carved out a nice niche for itself, and the involvement of one of C's original creators and Google backing it up sure did help out. And Golang has ignited a group of its own alternatives like Odin and Vlang.
Not sure where Hare can find a niche that isn't already occupied or how it's going to make people want to jump ship to them.
Rust is still not totally safe. There's quite bit of code out there that needs to be panic safe, run within a statically-sized arena, be guaranteed to always terminate/progress and so on and so forth. None of these things are ensured by Rust at present (and common language features like arbitrary function calls get in the way of them), while Ada/SPARK and the like at least make some effort to guarantee those.
But I don't "get it." Why would I choose Hare over plain old C? What are the motivating features of the language that make it "worth it?"
Remember: It's a pretty big hurdle to write something in a lesser-known language. If I had to convince a team of developers to choose Hare over C/C++/Rust, what would be the argument in Hare's favor?
It wont run because the QBE compiler doesnt have bindings to those OSes. Only x64.
Hare doesnt support Windows, so it will never rise to the level of C, Rust or Zig.
It just confirmed my initial thougts about this language. "A better C", for the people who respect it's ideology.
It's true that C++ is almost a superset of C, but most C programming idioms would be discouraged in C++.
As for Java - it's a whole different kettle of fish. Intended to run on a virtual machine with opaque high-level abstractions usable by your program; nothing like C.
(I don't know ObjectiveC enough to comment.)
And does the job well at implementation-defined int size and a char = u8 (there is a rune type, so why bother?)
Clang tidy[0] contains 67032 LOC from my calculations. Rust, Zig, whatever comprise millions LOC. Imagine if 1/10 was contributed to static/dynamic analyzers for C/C++ instead.
[0] https://github.com/llvm/llvm-project/tree/5da7c040030c4af72d...
This path is not constructive. Maybe instead of trying to be original, we can learn from mathematics where theories remain valid for thousands of years.
That's a pretty harsh and ignorant statement.
Those repositories made developer experience much more enjoyable than the mess that is the C/C++ ecosystem.
> borne of years of experience with these systems.
Which does not dismiss everybody else's experience.
All the package managers I've used have pros and cons, including using none at all.
The fundamental issue with security of NPM is the same issue you encounter in any package manager, you have to trust the maintainers. It's harder to trust a larger group of people than a smaller group of people. I'd argue the better solution with NPM is to choose libraries more deliberately and monitor your dependency graph instead of stopping to use NPM altogether.
Pypi is a mess. Python packaging is a mess. I would rather download and include Python source code by hand than learn all of those 3rd-party packaging "solutions".
The only saving grace Python has is it's vast standard library. So you don't have to reinvent wheels all the time.
EDIT:
Oh, and the C bindings, too.
So now instead of packaging a library once, you need to do it for every possible distribution?
https://drewdevault.com/2021/09/27/Let-distros-do-their-job....
This plays into Hare's philosophy on packaging.
Hare makes this process painless.
I understand not wanting to deal with the hassle of running a repo, but what about tooling around decentralized git repos? I can’t imagine distributions picking up Hare packages in larger numbers.
Obviously, C++ set the precedent and people are familiar with it. But if you're starting a completely new compiler, why make the same old mistakes with syntax?
A lot of languages made a mistake of using the dot for both scopes. You get used to it.
Not if you've swapped : and ;, like I have!
I think any new language (including Rust, Go, Nim, etc) that wants to displace C should not be trying to fix C's biggest mistake, it should be trying to replicate C's biggest success: simplicity.
Hence the growing popularity of Go as a C replacement. I look at Zig and Nim, and they look like viable replacements too.
Time will tell.
Fun fact: I've seen more Python projects being rewritten in Go than C projects being rewritten in Go.
Is Go a Python replacement?
Go seems to present simplicity as a selling point, so looks to be a natural next step for both C and Python programmers.
Every new language should fix every older mistake that can be done "for free", without growing the language too much or complicating things. C certainly has plenty of those.
What kind of magic is this?
https://harelang.org/specification.pdf
They are a very lightweight language feature which is mostly supplemented by the standard library, e.g. the strings module.
There is an example with freeing of strings in the introduction. Search for strings::freeall.
Inevitably, in the steady state? No
If some cataclysmic event occurs, perhaps with key industry influencers’ involvement? Maybe
https://en.wikipedia.org/wiki/Betteridge's_law_of_headlines
(And so does the article, FWIW.)
How is it that developers feel qualified to talk about subjects they understand so little of?
It's memory-unsafe, so you might as well use C++ instead, or if you want a "newer" language, then use Rust instead.
I don't understand the "static typing vs type inference" argument, so I'll not comment on that :) (there are plenty of statically typed languages with type inference)
"Pointer" types could just be library-based and architecture-specific. It doesn't really need to be part of the base language. This would make it easier to support things like GPU-bound code where general memory addressing isn't really a thing, or other features like multiple address spaces, segmented memory or the CHERI memory tagging extension.
All(ish) code used to be written in C and shell. To what degree had eg JavaScript replaced C already? I think quite a bit.
1. You don't need to engage with the community to use a programming language
2. The guys "ruining" conversations are a (loud) minority, most of the people I've talked with are light years away from proselytismlol "moral crusaders" who want software engineers to take a modicum of responsibility for their code