The Hare programming language
harelang.org
harelang.org
What I find a bit confusing though is the licensing. I belong to the “BSD school”, but can at least claim that I understand (and respect) how GNU looks at things. Looking at Hare’s licensing [1], I am somewhat stumped and my usual modes of thinking no longer apply. What is the threat that warrants this level of complexity? A commercial fork? A community fork? Proprietary drivers? Are these really realistic enough threats to warrant this level of license complexity rather than just stamping ISC and CC-BY on the whole thing and not worry about forks at all? Yes, there is some writing in the README, but perhaps I am too thick to “get it”?
[1]: https://git.sr.ht/~sircmpwn/hare#licensing
Lastly, a sanity check, am I reacting too strongly to this if I feel that it has a bit of chilling effect in terms of my excitement?
Sorry for the mess in terms of my writing. It has been months since I first saw the licensing and I still do not know how to internalise it properly.
If free software begets other free software simply through its replication, then I think the juice is worth the squeeze
Personally I'm very interesting in playing with it. Generics, functional programming constructs, and a lot of the syntactic sugar you find in modern languages have their uses but I think there's a lot of merit to the idea that you might not want them in a systems language. I think cleaning up some of C's rough edges and providing batteries while keeping things extremely simple and clear is very compelling. I'm also appreciative that it doesn't seem overly opinionated where it doesn't need to be.
It's kind of a bummer that there's no MacOS support so I'll have to SSH into my linux box to play with it. I hope the language takes off and support for other platforms materializes. Congratulations to everyone who worked on this.
Just a reminder (since I can't see a date in the blog post) that this was written almost a year ago. Lots of things have changed in the language since then.
Because to me it looks like the bulk of the criticism remains substantially relevant.
> I'm not yet sure whether this is a good thing or a bad thing. Sure, you can do clever shenanigans now, but something tells me this will make the code's control flow much harder to follow.
The most popular example I can think of is JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
Tbh, I think the opposite is true: without the ability to break outer loops, I have resorted to shenanigans using an auxiliary variable and then have to check it after the loop completes, obscuring the logic of the program. Of course, it does not have the full power of goto, and in this case I think it is a sensible choice.
Fair enough! Since writing that I've done a lot of Zig code, which has labeled loops, and I can say it's definitely made a lot of code clearer than it would be otherwise.
I don't know for sure, but I believe the JavaScript feature was inspired by Perl, which has had them since at least the v4 releases (1989-1993):
Why do people question whether something such as a programming language has business existing? Do they believe in a zero-sum theory of human attention? By "business existing" are they really questioning whether we should pay any attention to it? Serious question, because I find it somewhat disturbing on some philosophical level.
[1] I really want to refer this as to "untagged" union type, but of course this will cause a confusion with C-style unsafe unions.
I use Rust and like it well enough but it's amazing that its amount of complexity can work at all. It's healthy to have a counterpoint that stays clear even of generics.
I agree. Rust is very complicated and it still feels experimental. It is good to have more conservative languages.
Anyway, is it possible to target bare-metal with Hare? Is it possible to use it without the standard library?
Not among our target audience it won't.
> Anyway, is it possible to target bare-metal with Hare? Is it possible to use it without the standard library?
Yes. Here are two kernels written in Hare that don't use the stdlib:
I can appreciate the ideological and practical purity of that decision, but even Linux aficionados need to deploy code to other operating systems to make a living.
Some languages are just clearly designed to be used on the server.
Yes it will. I have no interest in using Linux as desktop but I do use it for deployments. If there is one trait of major PL is the adoption of the big 3 OSes.
This is Hare’s biggest flaw right now.
Keeping the scope niche/small seems like a feature to me, not a bug. Let non-FOSS OS users find something else (?)
I am personally no FOSS zealot but I can respect a project that has a firm idea of its scope and targeted user base and wishes to stay limited to that
Not everything needs to be huge or growth hacked to death
It seems like a trick that's worth learning.
Look how easy it's to try these. I don't have to set up VMs or even know what make is to do anything.
- dotnet: https://dotnet.microsoft.com/en-us/download
- rust: https://www.rust-lang.org/learn/get-started
- go: https://go.dev/dl/
If you WANT to be mainstream, you need to go the extra mile.
Dev on Mac and deploy on Linux is a popular setup for a reason. Until there's a macbook pro for linux that you can just buy*, it's going to continue to be popular.
* the XPS 13/15 are about as close as you can get
(at least, once they add drivers for the GPU!)
There are so many good laptops that work fine on Linux.
> you are not doing yourself any favors by not developing on the same platform as your deploy target
Why would I give up all the tooling I already have, such as an VS, to "do development on Linux"? Have you tried doing development from Visual Studio, VSCode on windows where it targets WSL? It's seamless and I don't really have to care much about Linux as a Desktop.
> code that targets Linux
This is too big of an assumption to make. For instance, the last company I worked for does contract with DOD where output of the project is two installers for windows an macOS, despite the product working flawlessly on Linux.
The is a HUGE world out there where your deployment targets won't be Linux servers, and this is a huge blind spot for a mainstream want to be language.
It sounds like you're not the target audience then?
Rather, I think it would be good because it makes a language much more palatable for mainstream adoption if it's possible for programs written to be ported to Windows/macOS. Languages need the positive adoption feedback loop. Rust would not be as mature and stable as it is today if it were relegated to Linux server programming.
That said - I can appreciate keeping a small target for the language in the beginning. It allows you to concentrate efforts and achieve an MVP much sooner than you would be able to if you had to deal with Windows BS :)
I suspect the pool of people who develop locally (on Win or Mac, and need something functional if not optimal) and deploy to Linux is not small.
Is there a particular reason? io-uring?
Small steps are steps never the less.
No gamedev?
i don't believe in wine/proton, and i find win32 to be trash
i don't like it, but windows is the platform, if you want to find players for your game
and i believe each platform deserve a native release
i'm not a fan of the mindset of having to lower your standard to please whoever got lazy to not support X, Y or Z
wine/proton promotes the wrong idea, that everyone shouldn't matter about the platform
https://harelang.org/tutorials/introduction/#using-yield
https://harelang.org/tutorials/introduction/#tagged-unions-i...
It is disappointing to see that "trust the programmer" is a design goal. Programmers can not be trusted with manual memory management. We have decades of proof, billions and billions of dollars of bug fixes and mitigation investments, real world damages, etc.
Building a language like this and saying you hope it will be the foundation for new operating systems is... depressing. It's setting us up for another century of industry failure - buggy software that makes users less safe.
It's not to say that memory unsafe languages have no place. Toy programs, or programs not exposed at all, are fine. But that's clearly not the case here - the stated use cases are things like the OS, "networking software", etc. All of the places where C has caused incredible harm.
edit: It would be wrong not to note that Hare does consider memory safety. https://harelang.org/blog/2021-02-09-hare-advances-on-c/
There are clearly wins here, no question in my mind that a world where spatial memory safety is the default is a better world than today. It doesn't change my view overall, however, that for the use cases defined that the bar needs to be higher.
I am also compelled to say something nice about the language. Most apparent is that it looks very approachable - I have to wonder what the '!' means (I can guess), but otherwise it looks very readable. I also like the explicit nature, that's my preference for programs as well as I find it's much more readable.
I think "simplicity" can be a tricky goal, but I like seeing languages call it out as one - I'm very curious to see over the next few decades how "simple" plays out.
I would ultimately just come out and say that we have to agree to disagree. I think that there is still plenty of room for a language that trusts the programmer. If you feel differently, I encourage you to invest in "safer" languages like Rust - but the argument that we're morally in the wrong to prefer another approach is not really appreciated.
That's certainly an improvement and worth noting, although it obviously leaves temporal safety on the table.
> but the argument that we're morally in the wrong to prefer another approach is not really appreciated.
Well, sorry to hear it's not appreciated, but... I think developers should feel a lot more responsibility in this area. So many people have been harmed by these issues.
Give us some time to see how Hare actually performs in the wild before making your judgements, okay?
> Give us some time to see how Hare actually performs in the wild before making your judgements, okay?
I'm certainly very curious to see how the approach plays out, but only intellectually so. As a security professional I already strongly suspect that improvements in spatial safety won't be sufficient to change the types of threats a user faces. I could justify this point, but I'd rather hand wave from an authority position since I suspect there's no desire for that.
But we obviously disagree and I'm not expecting to change your mind. I just wanted to comment publicly that I hope we developers will form a culture where we think about the safety of users first and foremost and, as a community, prioritize that over our own preferences with regards to our programming experience.
> to place anything on the chopping block in the name of security.
Straw man argument. I absolutely am not a "security maximalist", nor am I unwilling to make tradeoffs - any competent security professional makes them all the time.
> the #1 way to improve security is to reduce complexity
Not really, no. Even if "complexity" were a defined term I don't think you'd be able to support this. Python's pickle makes things really simple - you just dump an object out, and you can load it up again later. Would you call that secure? It's a rhetorical question, to be clear, I'm not interested in debate on this.
> I refuse to accept a doom-and-gloom the-cancer-which-is-killing-software perspective on this approach
OK. I commented publicly that I believe developers should care more about harm to users. You can do with that what you like.
Let's end it here? I don't think we're going to agree on much.
I really have to disagree on this, in spite of not being a security professional, because the history has proven that even a single byte of unexpected write---either via buffer overflow or dangling pointer---can be disastrous. Honestly I'm not very interested in other aspects of memory safety, it would be even okay that such unexpected write reliably crashes the process or equivalent. But that single aspect of memory safety is very much crucial and disavowing it is not a good response.
> [...] the #1 way to improve security is to reduce complexity, [...]
I should also note that many seemingly simple approaches are complex in other ways. Reducing apparent complexity may or may not reduce latent complexity.
And while maybe a Wordpress bug could "only" lead to a user password database leaked but not the complete system compromised, there is a valid question which is actually worse from case to case.
Point is just that from a different angle, things are maybe not so clear.
Software written in memory unsafe languages is among the most used on the planet, and could in many cases not realistically replaced by safer languages today. It could also be the case that while bug-per-line might be higher with unsafe languages, the bang-for-buck (useful functionality per line) is often higher as well (I seriously think it could be true).
By comparison memory safety bugs and shell script bugs mostly occur in specific classes of languages. It is therefore natural to ask for new languages in these classes to pay more attention to eliminate those sort of bugs. And it is, while not satisfactory, okay to answer in negative while acknowledging those concerns---Hare is not my language after all. Drew didn't, and I took a great care to say just "not a good response" instead of something stronger for the reason.
If managing memory lifetime is an inherently complex problem (which it is), the complexity has to live somewhere.
That somewhere is either in the facilities the language provides, or in user code and manual validation.
I think most programmers would agree with that sentiment. Getting everyone to agree on what is "responsible" and what isn't however...
Hare is a manifestation of the belief that in order to develop responsibly, one has to keep their software, and their code, simple.
An example of what I mean by this: An important feature of Rust is the use of complex compiler features in order to facilitate development of multithreaded programs and ensure temporal safety. In Hare programmers are encouraged to keep their software single threaded, because despite features like Rust's, concurrent programs turn out much more complex to write and maintain than sequential ones.
Keeping software single-threaded also eliminates many ways in which a program could fail due to lack of compiler enforced temporal safety.
I did find this page, though: https://harelang.org/blog/2021-02-09-hare-advances-on-c/
Which I found very interesting.
I believe one of the reasons Rust got so popular is that it made concurrency much easier and safer right at a time where the need for it increased significantly.
If that is the recommendation, maybe the standard library could focus on easily spawning and coordinating multiple processes instead, with very easy to use process communication.
Instead you want to respect the CPU under you and the way its caching and OoO instruction decoding work.
Don’t get me wrong, it is absolutely not directed at you, and I absolutely agree that we should strive for the simplest solution that covers the given problem, but don’t forget that essential complexity can’t be reduced. The only “weapon” we have against it is good abstractions. Sure, some very safety critical part can and perhaps should be written in a single-threaded way, but it would be wrong to not use the user’s hardware to the best of its capability in most cases, imo.
I'd wager that most of the applications that need to be "complex" in fact only haven't sorted out how to process their payloads in an organized way. If most of your code has to think about concurrent memory accesses, something is likely wrong. (There may be exceptions, like traditional OS kernels).
As hardware gets more multithreaded beyond those 16 core machines, you'll have to be more careful than ever to avoid being "complex": when you appreciate what's happening at the hardware level, you'll start seeing that concurrent memory access (across cores or NUMA zones) is something to be avoided except at very central locations.
> essential complexity can’t be reduced
I suggest looking at accidental complexity first. We should make it explicit instead of using languages and tools that increasingly hide it. While languages have evolved ever more complicated type systems to be (supposedly) safer, the perceived complexity in code written in these languages isn't necessarily connected to hardware-level complexity. Many language constructs (e.g. RAII, async ...) strongly favour less organized, worse code just because they make it quicker to write. Possibly that includes checkers (like Rust's?) because even though they can be used as a measure of "real" complexity, they can also be used to guardrail a safe path to the worst possible complex solution.
The languages that hide accidental complexity to the greatest extent are very high level, dynamic, "managed" languages, often with a scripting-like, REPL-focused workflow. Rust is not really one of those. It's perfectly possible to write Rust code that's just as simple as old-school FORTRAN or C, if the problem being solved is conducive to that approach.
I guess not having to type keywords in caps (because who uses IDE/smart editors) and having a C like syntax is the selling point.
Until we have some kind of significantly better solution that solves all memory management problems, I would rather work in a simple language that lets me carefully do everything myself, and if that language is also an improvement over C, I'm happy. However, that's just me, and I can fully appreciate that others are free to choose the tools that are good for them!
The problem is that you're them pushing other costs onto your users ie: exploitable software. So from the developer perspective, great, it works for you, but the cost is there.
I'm sympathetic to not wanting to use the other languages available, I'm not saying that any other language is doing things the "right" way, there's room for a lot of improvement. But I personally think that setting out to build new systems software in a memory unsafe language is setting users up for very serious harm.
Games are a bit different imo. While they're often networked they tend to not get attacked the same way as other software for a variety of reasons (though some games become so popular that it becomes worthwhile, like Minecraft). If a language set out to be "safer" (ie: improve temporal safety) but still prioritized performance, and emphasized its use case as being gaming, or explicitly for non-security-sensitive use cases, I'd be a lot more onboard with that. Jai seems to be driving towards that.
My issue with Hare is that it's presented (both on its page and in this HN thread) as being a language for general systems work.
Personally I'm a fan of C++'s unique_ptr/& as an unsafe escape hatch from Rust's single ownership or runtime refcounting overhead. It's at least as safe as Rust's unsafe pointers, and far more pleasant to use. Qt's QObject ownership system is reasonably ergonomic and QPointer is fun (though dangerous since it can turn into null unexpectedly), but Qt uses it pervasively (rather than only when safe memory management fails), relies on prose documentation to describe which pointers transfer ownership or not (resulting in memory management bugs), and QObject child destruction and nulling-out QPointers relies on runtime overhead. I haven't tried ECS or generational indexes yet, but those are popular in games, and Vale has its own ideas in this field (https://verdagon.dev/blog/generational-references).
On an aesthetic/principled level, I'd rather punt alias analysis to the programmer (pointer/restrict or &/&mut) rather than compiler complexity/magic (TBAA and provenance checking). Glancing at https://harelang.org/specification/, it seems Hare lacks an equivalent of restrict/&mut, and I wonder if that prevents the compiler from ever adding support for removing redundant loads/stores through pointers.
It's not that difficult, you just need to use UnsafeCell<…> or one of its safe derivatives (each of which has some potential runtime overhead) to keep the semantics tractable.
*mut T is harder to work with, this is UB according to miri since you didn't specify `&mut x as *mut i32 as *const i32`:
let mut x = 1;
let px = &mut x as *const i32;
unsafe {
*(px as *mut i32) = 2;
}
Problem is, most APIs won't give you a &UnsafeCell<T> but rather a &mut T. Not sure if you can convert a &mut T to a &UnsafeCell<T> (you definitely can't using `as`). If you want to create multiple aliasing pointers into a non-UnsafeCell type or struct field, one approach (basically a placeholder since &raw isn't stable, https://gankra.github.io/blah/fix-rust-pointers/#offsets-and...) is: let mut x = 1;
let px = addr_of_mut!(x);
unsafe {
*px = 2;
}UnsafeCell<T> also owns T, so transforming &mut T into UnsafeCell<T> also doesn't make sense. The unsafe equivalent of references is pointers.
> UnsafeCell<T> also owns T, so transforming &mut T into UnsafeCell<T> also doesn't make sense.
I wanted to transform a &mut T into &UnsafeCell<T> (note the &) and copy the reference, to allow shared mutation scoped within the lifetime of the source &mut T. How can this be accomplished?
Box deletes the owned object when it goes out of scope without being moved (like unique_ptr in C++). So if anything, you want to go the other way: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.f...
However, you can delete a *T by using <https://doc.rust-lang.org/std/alloc/trait.Allocator.html#tym...> with the Global allocator (since this is the one you're most likely using).
> I wanted to transform a &mut T into &UnsafeCell<T> (note the &) and copy the reference, to allow shared mutation scoped within the lifetime of the source &mut T. How can this be accomplished?
If you want to have two instances of one &mut T, you don't go through &UnsafeCell<T>. Instead you may cast &mut T into *mut T and then use this: <https://doc.rust-lang.org/std/primitive.pointer.html#method....>. This however will cast into any lifetime, so if you want to bind the two lifetimes together, then you need to have the lifetime of the original &mut T explicitly specified, and then you assign the result of the method I linked to a variable with explicitly specified type where you specify the lifetime annotation. Alternatively, you may write a separate function which accepts both references as arguments and binds the two lifetimes together the usual way.
I admit it's a bit unergonomic. The best way currently would be to have the data stored as UnsafeCell in the first place and then call get_mut() on it to get all the references. However, if this reference comes from outside, you're left with the little mess I outlined above.
That would certainly be nice, but the state of the art on what problems even are is far ahead in optimizing compilers than anyone else - "having a restrict keyword" doesn't solve every aliasing problem afaik, and nobody respects the performance people when they tell you undefined behavior in C is actually useful. So nobody has come up with a simple solution for a better language that solves problems like pointer provenance and yet is "faster than C".
Actually most people's ideas of how to make programs faster are complicated things like autovectorization that don't work and would make it slower.
Quite a few languages have value types now, with that you can restrict your usage to stack allocations for the critical hot loops, while low-latency GCs promise less pauses than the OS itself, which should be plenty good for even the most demanding games.
[0]: https://ebiten.org/blog/native_compiling_for_nintendo_switch...
But regarding GC, Java is unquestionably the king in that aspect, throughput-wise G1 is unbeatable and its relatively new ZGC might be of interest to use. It is the one I thought about previously, it currently promises sub-millisecond max pause times and this pause time doesn’t grow with heap size. Unfortunately Java doesn’t have value types yet, so you either write your hot loops with only primitives and allocations you make sure gets optimized by the escape-analyser, or do some manual memory management with the new Panama APIs, which are quite friendly in my opinion.
EDIT: Just read your link, while Java can be AOT-compiled with GraalVM, only the enterprise version supports some of the more exotic GC-variants (though not sure about ZGC). It should be free for personal use, but do have a look at it. Though what I wrote concern mostly running code with the JVM.
The JVM isn't the most common game dev platform, but I have been enjoying using LibGDX with Scala on jdk 17 with ZGC.
[0] https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html
The main feature is its ability to safely share or move data around between actors in a way that is data-race and deadlock free. Pony doesn't use locks anyways :-)
A high level as to how it achieves this:
i. All variables have a "reference capability" - which is a description of what you can and cannot do with the variable alias (pointer) you have.
ii. The references to the data (think pointer) are passed in messages.
iii. As references are made and removed, actors send messages to the originating actor updating the count of references. When the count reaches zero, that data can be GC'd.
It's nice, because unlike some other language runtimes, it doesn't have to stop the world to work out what can and can't be GC'd. It's all done on-the-fly as it were.
It really isn't. Not compared to C++, at least. Or to managed language runtimes, which are just as "large and complicated", only beneath the hood.
System level programming almost by definition requires quite a bit of complexity, and you can’t hide it no matter how elegant your abstraction is. Essential complexity is non-reduceable:
Rust may not have SFINAE but C++ doesn't have for<'a>, Phantom data or Pin.
I'll grant you PhantomData, but I'd argue with the other two. C++ does have lifetimes and pinning semantics, they're just implicit and (largely) taken for granted. That is, until you cause memory unsafety with either.
IMO, the overall pattern between C++ and Rust is that "advanced" use requires many of the same skills between the two, but that (1) Rust is much better about avoiding "advanced" use, and (2) Rust forces the user to be much more explicit about what they actually mean (cf. lifetimes and pinning). These are arguably more complex than what C++ does, but only in the sense that C++ amortizes that complexity in blood.
C++ does have pinning, but it is much rarer than in Rust because of user-defined move constructors.
The gist of Rust on this fairly easy, is the heritage/chasing of C++ that makes Rust complicated.
A "Rust simple like pascal/c" have potential and I bet 1 billon the borrow checker will not make it hard to use (check https://vale.dev)
This has been tried, see Cyclone. It was a lot less practically usable than modern Rust.
Hare tries to be simple, so that it's easier to reason about the code and hence maybe find/avoid such bugs more easily.
It's noteworthy that Google is financing the effort to bring Rust to the Linux kernel, that Microsoft is also investing in the language and that there are newer, production usage focused operating systems written in Rust. (eg Hubris [1])
Hubris is intended to run on microcontrollers in a very low-level context (e.g. no display), so is very unlikely become a desktop / user-facing OS.
(I work at Oxide, mostly writing Hubris code)
Your assumption is that memory safety has to be baked in the language. It could be baked into proof assistants that are part of (optional or add-on) tooling, like what sel4 does. A simpler language makes this more possible, and the things that a proof assistants can do go far beyond what rust is able to provide, without sacrificing compilation speed or other forms of optimality (e.g. avoiding the heap) when you don't want or need such a high level of security guarantee (e.g. writing a cli tool that never sees the internet)
As for me, I'm terrified that rusts complex macro system will hide/obfuscate discovery of other forms of security regression, like timing or energy side channels.
I'm not interested in discussing Rust. Frankly, I'm sure there will be plenty of other people already doing so.
What's clear from this thread is that Hare does attempt to move the needle, relative to C, with regards to safety. My opinion is that that's not enough for the use cases they're targeting, but I suppose it's really up to whoever's writing the software to decide that.
This is false. Rust’s borrow checker is nothing else but an included proof assistant for rust code. The reason it can catch so many memory issues and data races is specifically due to a more restricted language. Also, sel4 is a relatively tiny program which was written for an unusually long time by domain experts. Formal verification simply doesn’t scale to global properties, that’s why some restrictions are useful as they allow local reasonings instead.
For a more hands-on example look at the quality of auto-complete in case of Intellij’s Java vs a dynamically typed language. This night and day difference in quality is yet again possible due to what the language can’t denote.
Re rust macros: I don’t get your point, AFAIK they simply expand to regular old Rust code and then gets compiled, so the exact same safety guarantees apply.
> so the exact same safety guarantees apply.
I see this a lot with Rust, and with encryption. There are no magic bullets. There is no "you are safe because you used this" tool.
In this case, Rust's safety gaurantees apply only to memory safety.
GP wasn't talking about memory safety above.
I don’t mean to say that a macro can’t get needlessly complex, but the same is true of functions that are inherent in basically any language. In the worst case macros can be expanded and looked at in their full forms. They are as always abstractions, which are pretty much necessary, but they can be abused as well.
This is an assertion you are making with absolutely no evidence, and also totally self-contradictory with your statement "Rust’s borrow checker is nothing else but an included proof assistant for rust code".
While we're at it, also Ada does this, which has long been used for large scale mission critical applications where formal assurances are necessary (with even more available optional safeties than rust provides).
Proof assistance is sort of irrelevant. Types and proofs are related, as denoted by the curry howard correspondance.
The real issue with "throwawaymaths"' point is that they're saying "use proof assistants" to people who are using proof assistants. SEL4 is a terrible example of a success story, as it took ages to complete, and then there was immediately a bug found in a class they weren't looking for - because rice's theorem.
They're clearly advocating for the use of specific and explicit proof assistants, which is fine and a totally reasonable thing to advocate for, but in no way is related to rust or the discussion, which is why I chose not to engage.
And I don’t believe my claim is unsupported, the largest formally verified program is the mentioned sel4, which is still tiny compared to even the smallest of business apps and was written by domain experts over multiple years.
Restricting a problem to a subset is like the numero uno step to solve any hard problem - and this is what rust basically mandate. It won’t provide bug-free programs, but it can reliably prove the absence of memory bugs and data races due to the borrow checker, which can do its work on function-scope, since all the relevant information is encoded in the function’s generic lifetime arguments.
I'm "wholly unimaginative" about what variables can be acceptably corrupted; I can only think of deliberately reading uninitialized memory as a randomness source, a case that is easier to prevent (by clearing allocated memory by default on the OS side) than to enable.
I’m actually doing my PhD on the verification of Rust programs and wanted to add that the opposite is actually true. The type system of Rust helps to create a much simpler model of the language which allows us to do proofs in much larger scale than with C (for the same effort). This is specifically because of how ownership typing helps us simplify reasoning.
We have sanitizers if you are a bad programmer, use that if you don't trust yourself
By your own words you are definitely a bad programmer if you have ever written more than 1000 lines of code in low level programming language, because there is simply no way you haven’t made an error. You just don’t necessarily know about it, which in my opinion makes you a worse developer, especially with this ancient and destructive mindset.
We have the tools to eliminate memory bugs already
Forcing people to use a language with a buitin sanitizer/babysitter that runs everytime you compile your code and as a result makes you wait 10+ minutes between each line of change is dumb
Expecting your code to be bug free because the code written by someone else told you so is also dumb
https://github.com/rust-lang/rust/issues/38899
who to trust in that case?
--
educate and trust developers, use tools to audit your code when needed
And your 10+ minutes baseless assumption is just demonstratively false. In the default debug mode it is literally instant with human perception on my current, not small project. Longer compiles only happen in release mode or when you introduce new dependencies.
Shall I link you to Valgrind bugs as well?
Ok, not 10+ minutes, more like 6 minutes on a beefy machine:
https://www.youtube.com/watch?v=nR2WDBMjkh8&t=976s
> Shall I link you to Valgrind bugs as well?
See, you fail to understand the point
It's much better to push logic from being manually re-written over and over by tens of thousands of different programmers, to being built into a language/tool/library by a team of a few hundred experts and then robustly tested.
Nope, doesn't work. Then you have to trust that the programmer who wrote the assembler didn't make any mistakes. Real programmers program in octal.
This is true, but the actual solution is safer assembly. ARM is heading in this direction with PAC/BTI/CHERI. Intel, being Intel, tried adding some and it was so broken they removed it again.
It could go much further.
Why are you afraid of manual memory management?
The world is just doing fine with it, because, believe me or not, the world's worst problems like violence, war, poverty, famine, just to name a few, are not caused by C bugs.
First and foremost, does it provide any primitive that can be used for automation? C++ brought destructors, Rust did a Drop trait. What do you have?
Second, does it provide any way to operate on types? C++ got templates, Rust offers generics. What do you have?
Third, does it provide useful compile-time logic?
If it lacks those, what does it bring to the table to make up for those gaping holes? This is not the '70s, or even the '80s. We have learned things since then. Have you?
We have learned to read the documentation before we judge things.
One choice is to just ignore everything, which almost always produces the right answer. But that will miss the thing that would have been worth looking into. Another choice is to try ways to filter out chaff. What is not a possible choice is to dig into everything that might conceivably be interesting.
One thing we can always be sure of: if it is meant to appeal mainly to C coders, it will fall flat, because everybody who is still using C has seen a thousand languages go by and passed on all of them.
If in fact the language would have turned out to be interesting, helping out early might make the difference between its fizzling out or getting traction. And, helping out might be a chance to learn a lot, or to ensure it will scratch your itch.
In this case, for me, none of that seems likely. As usual.
Fair, to a point. But writing an HN comment is a bit lower investment than learning a new language well enough to evaluate it fairly.
> If in fact the language would have turned out to be interesting, helping out early might make the difference between its fizzling out or getting traction.
You may have that kind of pull; I don't. Nobody is going to care whether I like a language or not.
> And, helping out might be a chance to learn a lot, or to ensure it will scratch your itch.
That's somewhat more likely than me helping it gain traction. And, in fact, the earlier the input, the more influence it has at bending the language in the direction you want, because there's fewer voices at that stage. But there are too many potential languages for me to do that with very many of them, so we're back at the problem of choosing which ones to invest in...
(I once did take your approach, with the C++ STL, when was first announced on the newsgroups. It wasn't part of the compiler yet - it was a separate download. I found a bug in the initialization of the random number generator used to random-shuffle vectors. I, some random nobody on the net, emailed Stepanov and Lee, and got three fixes in the next two hours. I was amazed at the response. And the fix made it usable for what I was trying to do with it.
I think that's the only time I've invested in something brand new, though...)
I meant: one could do some of the work needed to make it ready for use. If not done, the language fizzles. Fizzling is the normal fate of any language, absent the miracle.
Hare looks a tiny bit better than C. That was D's problem: it was a tiny bit better than C++; now C++ is much, much better than what D had targeted. If you make Hare enough better than C to merit attention, it will be different enough for the C stalwarts to reject, but not powerful enough for C++ and Rust refugees to wash up onto.
A language made by people who consciously make that tradeoff is almost certainly not worth even trying to learn. At least, if you're looking for a language that will actually act like a force-multiplying lever and save you time. Like, you know, programming languages are meant to do.
Maybe someone not on the team will write a decent review of the language, and we will find out more. Hopefully that person will read the documentation and actually try out the language.
In which case, the developers of this language are demanding that I spend my valuable time trying to read through their documentation to figure out if this language is good for me, in which case I reply: my time is also a gift and you have no right to demand that of me - I'm going to go and look at another language, and suggest that friends and fellow developers do the same.
They really need a section on the landing page to compare Hare to Zig. (And to C, for that matter.)
Or macros, not even textual C-style ones. Non-starter for me.
Not allowing macros means a more standardized reference implementation, which translates to a more readable code overall.
Nicer, AST-level macros can be better, but can also be a pain (see Lisp, C++ for examples of either).
And yes macros are dirty, but yet they're often far simpler than the alternatives, based on how languages like C++, Zig et al have attempted to reduce them.
There will always be shortcomings in any language, and there will always be situations that need to be fixed with a quick hack, even though that is not a long term solution.
And in almost any project I have a few lines of macro magic that almost completely fixes situations where I would otherwise have to resort to terribly complicated C++ templates and slow compile times.
Another day another group of people criticising a language they’ve never tried.
If you paint a house grab a paint brush. If you need to hang a picture a hammer and a nail.
Writing a program? Pick the best language for the job.
Andrew might be able to expand on this.
https://git.sr.ht/~sircmpwn/hare-examples/tree/master/item/s...
We have some planned improvements which will make this easier still. We'd like to improve the build driver's ability to link with C libraries via pkg-config, and automatically rig it up when you depend on C interop modules like hare-sdl2. Code generation to automatically provide bindings going either direction is also something we'd like to work on.
Zig goes, imho, a little bit too far. Hare has no intention of providing a built-in C toolchain, and does not treat C as more special than any other language for interop. It's a question of scope.
Going the other way and calling Hare code from C is not that smooth yet, but there are plans to improve that in the future.
Does it do something better than any other language?
Hare attempts to improve upon C while still remaining a very simple language, with much better error handling, support for arrays and slices, a rich but still minimal standard library, more concise and powerful function return values using tagged unions, improved memory safety, and other things you can find here:
* https://harelang.org/blog/2021-02-09-hare-advances-on-c/
* https://harelang.org/tutorials/introduction/
Disclaimer: I worked on Hare and this is just my personal opinion. I hope everyone can judge whether or not they like Hare for themselves by playing with the language!
If I were to speak generally about Hare compared to other efforts, I would focus on its simplicity and stability goals. It's the only new language in this space that's arguably simpler than C, in my opinion, and the goal is to provide a small, stable foundation that can be depended on for a long time, much like C is. Unlike many other languages in this space, its trade-offs also tend to position it to do anything C can already do - Rust has famous issues with linked lists, ownership when linking to things like libwayland, and they have their hands full getting into Linux; Go's runtime and GC rules it out for many applications; and so on.
Hare's design is a lot different from Zig's as well. Hare lacks comptime and generics, and does not target non-free platforms like Windows and macOS.
However, the target audience and supported use-cases for the two languages is similar. It mostly comes down to a matter of preference for most people.
I would be extremely surprised if this were the case, especially given that the Zig standard library is full of generics.
The same can be said for why do we need to put parenthesis after functions that take no parameters? So people still know it's a function.
I’m with OP here. C# proves that . reduces the noise substantially.
I don't know enough about C# to know if it matters. From everything I know, it's a really well designed language, so I'm guessing they made the right call.
It doesn't mean it's the right call everywhere.
In the Ruby world, I think they have the convention of writing `class#method` in docs/comments when mentioning an instance method, and `class.method` when mentioning a static method.
i.e https://docs.microsoft.com/en-us/dotnet/api/system.console.w...
let x: int = 0;
It's so that the compiler can distinguish between those two, and likely some other areas too.https://harelang.org/tutorials/introduction/#tagged-unions-i...
They are also covered in section 6.5.18 of the Hare specification:
https://harelang.org/specification.pdf
Would be curious to hear your thoughts on how they compare given your (presumed) background in functional programming.
type foo = (A int | B int)
Which from my cursory reading does not seem to be possible? type a = int;
type b = int;
type c = (a | b);
Which seems to be what you're angling for here? I also suspect that you're approaching this from a Rust- or Zig-like background where enums and tagged unions are closely related; this is not so in Hare.The example you linked did not really make it clear from my point of view because one of the points is that it will collapse if there are multiples of a specific type. But your way seems to make it possible.
> Would be curious to hear your thoughts on how they compare given your (presumed) background in functional programming.
I'm just a student with an interest in programming languages and very little knowledge and experience, so I don't think I'll be able to provide any significant insight ;)
There are sum types (tagged unions), but product types (tuples) were a late addition and we still don't have support for tuple unpacking or pattern matching on them.
Tuple unpacking is something that's been put off for very long but looks like it is going to finally happen soon. Pattern matching on types other than tagged unions hasn't been completely ruled out yet, but is also not guaranteed to happen.
There's also an "fs" module which provides a filesystem abstraction:
The "os" module links these with the host operating system. It provides an implementation of the fs abstraction for the current working directory in the host filesystem, and also provides convenience wrappers like os::open, which calls fs::open with the host filesystem singleton.
Metaprogramming is necessary in order to compensate for any missing language features, it doesn't add much complexity, and it's not here.
Isn't that true of all languages?
https://docs.harelang.org/unix/poll
You can see a small event-driven server example here:
https://git.sr.ht/~sircmpwn/himitsu/tree/master/item/cmd/him...
https://git.sr.ht/~sircmpwn/himitsu/tree/master/item/cmd/him...
let iter = os::iter("/")!;
for (true) {
const entry = match (fs::next(&iter)) {
case let ent: fs::dirent =>
yield ent;
case void =>
break;
};
fs::println(entry.name)!;
};We don't plan to add anything like that. Hare strives to be simple and straightforward. Metaprogramming would not fit it well.
Closest thing in this direction that we tried was reflection, but that didn't turn out to play along with the rest of the language either, so it was removed.
Looks like a very simple, yet readable little language. Reminds me of C's glory days.
I know that we already have a couple of "better C" languages out there, but Hare seems like it truly grasps the simplicity of it's older cousin.
I love the focus on open-source only, by the way. It will keep the spirit of the language. I just hope that Drew will stay on this project for a while longer. A language with this kind of vision must persevere.
Good luck to Drew and the Hare team!
* https://git.sr.ht/~sircmpwn/hare/tree/master/item/cmd/harec
* https://git.sr.ht/~sircmpwn/hare/tree/master/item/hare
* https://harelang.org/blog/2021-03-14-a-self-hosting-toolchain/There is one more significant (but not user-visible) change planned for the language before we continue working on that.
The ability to do the latter is expected to result in a significant performance improvement and stack usage decrease.
Nowadays, self-hosting your compiler doesn't demonstrate much of anything. It takes a lot more than a compiler to prove anything meaningful about your language. Time spent on self-hosting is mostly just time wasted. It mainly suggests you were not really serious.
Make a front end for LLVM, and get on with things.
You're reading way more into my comment than I wrote. I asked a question.
Your new language desperately needs libraries that bring it up to a level of practical usefulness. A compiler front end is about the least-useful code you could write in it. Taking a long detour for that says something, but it is the wrong thing.
That's the whole point of creating a new programming language.
It could be fixing shortcomings of the languages without a huge complicated system that needs to be built into the language (and if it isn't, you can't just switch languages).
It could be dirty hacks that abstract a problem at the syntactic level (this is the kind of macro that is most likely to be replaced).
C macros operate on arbitrary tokens, which get converted to code or constants. For example variadic functions can be implemented to act on token arglists that act as abstract tuples: https://github.com/FrozenVoid/C-headers/blob/main/argmanip.h
It's an explicit goal of the language that you can tell what the local control flow is by reading the code, but of course one is free to run the C preprocessor as part of the build if one disagrees.
I don't think C++ constexpr/consteval can be used to write things like generic json serializers and deserializers, which Zig does in the standard library using comptime, but I'm not sure.
If all you want are textual substitutions, you can use the C preprocessor in front of any language.
You will be paddling upriver if that language doesn't have a mostly C-compatible token structure, or has white space sensitivities that C preprocessing doesn't preserve and such.