Interesting to see Fil‑C used with a large framework like Qt. The fact that it compiles with minimal changes says a lot about the compatibility layer and the InvisiCaps approach.
AFAIK Fil-C does not catch all memory safety bugs, for example some use-after-free are just not bugs but work as intended (you still access the original data/allocation). This means that it's not a sanitizer and code that runs fine on Fil-C may show UB when run normally.
eh, I daily-drive a -fsanitize=address -fsanitize=undefined build of Qt and actual memory bugs are almost never a thing - I think the only time I had some were in tooling executables such as qmllint, but not in the framework itself. Most of the bugs by large are more "behaviour" bugs.
ubsan definitely has some warnings due to a few "technically UB" patterns used as optimizations in Qt as they are known-working on every target platform under the sun, but yeah, no crashes :)
I'm only using this configuration for the software I develop though (+ libc++ debug mode) as it's painfully slow, but it exercises the Qt codebase in depth.
Depends in which sense you want it to "catch" the bugs. As this readme notes/quotes,
> All memory safety errors are caught as Fil-C panics.
If your problem is a memory-based bug causing a crash, I think this would just... catch the memory-based bug and crash. Like, it'd crash more reliably. On the other hand, if you want to find and debug the problem, that might be a good thing.
Sure, if the memory error is an immediately crashing one like a null per deref, but if is (for example) a memory corruption (e.g. an out of bounds write or a write-after-free) then this would be super helpful in exposing where those are happening at the source.
That’s what “catch” means here. As in, catch it in the act. Tools that make bugs crash more reliably and closer to the source of the problem are extremely valuable.
Most large C code bases aren’t really written in C. They’re written in an almost-C that includes certain extensions and undefined behavior. In this case, it uses inline assembly (an extension) and manipulating pointers as integers (undefined behavior).
While I’m always thankful when people give the broad perspective and context in a discussion, which your comment does. The specifics of this particular project’s usage of almost-C is not something I could have quickly figured out, so thanks. For such a large program, an to be as old as Qt is at this point, I find it impressive and slightly amazing that it has in some sense self-limited its divergence from standard C. It would be interesting to see what something like SQLite includes in its almost-C.
The more portable a project is, the less weird stuff it’s likely to do. The almost-C parts become more of a headache the more OSes and compilers you support. This seem pretty tame, and I’d expect SQLite to be similar. I work on some projects that only support a single OS, compiler, and CPU architecture and it’s full of dependencies on things like the OS’s actual address space (few 64-bit archs use all 64 bits).
Sure fooled me. I follow his Twitter account and there isn't much he hasn't got building with it at this point. UX comes later. Amazing it's the random work of one person
The author wrote WebKit’s allocator and worked on JavaScriptCore for over a decade. I really enjoyed his posts on the WebKit blog over the years like this one on the concurrent garbage collector (2017) https://webkit.org/blog/7122/introducing-riptide-webkits-ret...
I don’t think so much is fil-c itself, but from the looks of the diff it’s a new platform essentially. That can require porting existing software generally which you can read from the posted diff
In my experience (20+ years with C/C++, and about 4 years with Rust), Rust is significantly less complex than C++, while being similarly capable. The extra syntax that throws off so many C++ devs is almost exclusively about data types and lifetimes, which I find very useful for understanding my own code and others', and which I wish I had in C++.
> I like the concepts proposed by Rust but do not like fighting with the borrow checker or sprinkling code with box, ref, cell, rc, refcell, etc.
I'm not sure why this would be confusing or disliked by a C++ dev.
Rust's Box<T> is similar to C++'s std::unique_ptr<T>.
Rust's Rc<T>, Arc<T>, and Rc<RefCell<T>> serve similar uses to C++'s std::shared_ptr<T>.
Rust's Weak<T> is similar to C++'s std::weak_ptr<T>.
Verbosity of both is nearly identical. The big difference is that Rust enforces the rules around aliasing and mutability at compile time, whereas with C++ I get to find out I've made a mistake when my running code crashes.
> Great, if you are right everyone is going to be using Rust eventually.
Every task does not need speed and safety. Therefore, "everyone" doesn't need Rust.
But I could easily see a future where C++ is relegated to legacy language status. It has already had decades of garbage-collected languages chipping away at most of its general-purpose uses, but Rust seems capable and in a position to take away most of its remaining niches.
It's kind of why the old C++ programmer that I am decided to learn Rust in the first place - seemed like a good idea at the time to skate where the puck is heading.
The real answer should have been a new language that has memory safety without all the extra conceptual changes and orthogonal subsystems that Rust brings. The core value of safety did not need the reinvention of everything else with the accompanying complexity and cognitive load. For example Zig which instead of introducing a new metaprogramkming language, it uses...... Zig - imagine using the same language instead of inventing a new additional language with all the accompanying complexity and cognitive load and problems. Rust is for those who revel in complexity. And traits - traits and extra complexity not needed for safety. And result and option and move by dedfault - none of these things were needed but they all add up to more complexity and unfamiliarity and cognitive load. And when you add it all together and intertwine it you end up with something so unfamiliar that it no longer looks like "ordinary programming" it looks like something from the Cambrian period.
As a C++ developer, my experience with learning both Rust and Zig is that they're both good languages, and any reasonably skilled C++ developer could learn either language if they put their mind to it.
If you forced me to pick between Zig and Rust for a long-running project though, I'd pick Rust 10/10 times for the simple fact that it has been stable for more than a decade and already has momentum and funding behind it. Zig is a cool language - one that I've actually written more of than Rust - but it hasn't hit 1.0 yet and still has significant churn in both the language and standard library.
That is not a statement anyone can deny. Unless you're a Rust-bro "hey man I learned it so I’m baffled how you can't see it's super simple ..... etc etc" - often the implication that you're not very smart if you think Rust is complex.
Of everything that I have learned about programming, this is the BIGGEST lesson: - complexity is bad, avoid complexity. Complex is not the same as "sophisticated", which implies necessary intricacy. Complex means unneeded unnecessary cognitive load - things made harder than they should be when it could have been avoided - that's complexity. If you are writing complex code then you're writing bad code. And Rust is complex.
It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
What we needed was Rust-- i.e Rust without all the non-safety related extras that make it different - it's all the non-safety add ons that makes Rust into a Rube Goldberg machine.
> It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
We did, arguably. Those languages are called JavaScript, Python, Java, C#, etc. Those languages tend to be eschewed in certain niches, though, and it's there that simplicity tends to be harder to achieve.
> What we needed was Rust-- i.e Rust without all the non-safety related extras that make it different - it's all the non-safety add ons that makes Rust into a Rube Goldberg machine.
I think it might also be worth considering that some people find those "non-safety related extras" a good thing. Dropping backwards compatibility and/or familiarity, just like everything else, is a tradeoff, and that tradeoff might be worth it if you think the resulting semantics are nicer to work with.
> It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
I am not a savant by any means, and yet I was able to get up and running with Rust relatively quickly. What Rust isn't is ergonomic, in that Rust gets very annoyed with the ways that one might want to structure their code. Trust me, I got bit by the borrow checker countless times, and it did grate on me.
As a result, there are many tasks that I would avoid using Rust for, tasks where both speed and safety aren't critical. But if neither is a priority, the list of alternative languages I can resort to is quite long, much longer than Zig, C++, or C. And in the cases where both are a factor, I would consider being needled by the compiler to be a feature and not a bug.
You'd be surprised to see, after deep inspection, how little of Rust you can remove while keeping its safety story the same (that is, memory safe without GC).
Traits? Nope. We need some way for code reuse. Classes cannot be made memory safe without extra cost (at least, I don't know how can they). And they are not less complex either. Templates like C++? More complex, and doesn't allow defining safety interfaces. No tool for code reuse? That will also severely limit the safety (imagine how safe Rust was if everyone would need to roll their `Vec`).
The borrow checker of course cannot be omitted. ADTs are really required for almost anything Rust does (and also, fantastic on their own). Destructors? Required to prevent use after free.
Async can be removed (and in fact, wasn't there in the beginning) which is a large surface area, but even today it can mostly be avoided if you're not working in some areas.
I don't think anybody can deny Rust is complex, but most often it's inherent complexity (what you call "sophistication") given the constraints Rust operates in, not accidental complexity.
You focused on the C++ aspect and completely failed to engage with the actual critique - what is a “simple” language that you’re evaluating Rust against as a failure?
> The real answer should have been a new language that has memory safety without all the extra conceptual changes and orthogonal subsystems that Rust brings. The core value of safety did not need the reinvention of everything else with the accompanying complexity and cognitive load.
What would the minimal set of features be, in your opinion?
> For example Zig which instead of introducing a new metaprogramkming language, it uses...... Zig - imagine using the same language instead of inventing a new additional language with all the accompanying complexity and cognitive load and problems.
Zig probably isn't the best comparison since Zig doesn't try to achieve the same level of compile-time memory safety guarantees that Rust aims for. For instance, Zig doesn't try to statically prevent use-after-frees or data races.
That being said, as with everything it's a question of tradeoffs. Zig's metaprogramming approach is certainly interesting, but from what I understand it doesn't offer the same set of features as Rust's approach. For example:
- Zig's generics are more similar to C++ templates in that only instantiated functions are fully checked by the compiler. Rust's generics, on the other hand, are completely checked at the definition site so if the definition type-checks the author knows it will type-check for all possible instantiations. Rust's approach also lends itself to nicer error messages since everything a generic needs is visible up front.
- Zig's comptime isn't quite 1:1 with Rust's macros. comptime is for... well, compile-time computation (e.g., reflection, compile-time branching, or instantiating types). Macros are for manipulating syntax (e.g., code generation or adding inline support for other languages). Each has things the other can't do, though to be fair there is overlap in problems they can be used to solve.
In any case, metaprogramming approaches are (mostly?) independent of memory safety.
> And result and option and move by dedfault - none of these things were needed but they all add up to more complexity and unfamiliarity and cognitive load.
I don't think Result/Option are that complex (if at all) since they're trivially derivable from discriminated unions/sum types/enums.
I'm also not sure how move by default is necessarily "more complexity... and cognitive load"? Maybe as a result of unfamiliarity, perhaps, but that seems more a property of a person than a language, no?
There's also https://docs.rs/proc-macro2/latest/proc_macro2/ which effectively allows for the construction of procedural macros with normal Rust possible. As well as enable the use of proc_macro types in normal rust. It'll make it to stable someday, I hope.
> The real answer should have been a new language that has memory safety without all the extra conceptual changes and orthogonal subsystems that Rust brings.
So what you're saying here is that you don't understand that Rust's rules around memory ownership, aliasing, and mutability are what allow the language to provide deterministic compile time memory safety without runtime cost. If you figure out another way to guarantee memory safety at compile time with zero runtime overhead, you should write a paper and start another language around it!
I don’t know what you mean by SafER but it’s important to remember that Fil-C sacrifices a lot of performance for that safety which detracts the reasons you’d be running that software as otherwise C was a bad language for them. Sometimes this won’t matter but there are places fil-c won’t be able to go that Rust can - embedded and OS kernel come to mind. Other places would be things like browsers or games. Rust gives you the safety without giving up the ability to do performance.
Also, I could be wrong but I believe any assembly linked into Fil-C bypassed the safety guarantees which would be something to keep in mind (not a big deal generally, but a source of hidden implicit unsafe).
I’m apparently comment happy on this OP, but, the typing of it looks funny because it starts the sentence, I’m pretty sure OP was saying safER, as opposed to SAFE (as in totally safe instead of comparatively safer). I have been quite charitable to OP in some sibling comments and will do so here. I think OP is attempting to give Fil-C some credit for being an attempt to increase the overall memory safety of existing code without incurring the complexity of a new language or the complexity of rewriting long running/widely distributed code. It is a decent sentiment and a viable methodology to achieve a laudable goal, but is certainly susceptible to caveats like the performance penalty you mention.
I’m going to start this comment by specifying that I don’t know what OP was considering complex about Rust and, unfortunately, a large amount of discussion on the topic tends toward strawman-ing by people looking to argue the ‘anti-Rust’ side of said discussions. Additionally, the lack of a contextual and well considered position against some aspect of Rust, as a language, is very common, and at worst the negative take is really just a overall confrontational stance against Rust’s uptick in usage broadly and its community of users, as perceived (and also strawmanned), in a generally negative light. But since borrowing is not explicitly mentioned by GP, I will give a slightly different position than he might, but I think this is an interesting perspective difference to discuss and not a blatant ad hom argument used to ‘fight’ Rust users on the internet.
From my position the complexity incurred by ownership semantics in Rust does not stem from Rust’s ‘formalization’ and semi-reification of a particular view on ownership as a means of program constraint. The complexity of Rust, in relation to ownership, comes with the lengths I would have to go to design systems using other logical means of handling references (particularly plain hardware implemented pointers) to semantic objects: their creation, specification, and their deletion. Additionally, other means of handling resources (particularly memory acquired via allocation): its acquisition, transport through local and distributed processes (from different cores to over the wire), and its deletion or handing back to OS.
Rust adopts ownership semantics (and value semantics to a large degree) to the maximum extent possible and has enmeshed those semantics throughout all levels of abstraction in the language definition (as far as a singular authoritative ‘definition’ can be said to exist as a non-implementation related formalism). At the level of Rust the language, not merely discussions and discourse about the language, ownership semantics are baked in at a specified granularity and that ownership is compositional over the abstraction mechanisms provided. These semantics dictate how everything from a single variable on the stack to large size allocations in a general heap to non-memory ‘resources’, like files, textures, databases, and general processes, are handled in a program. On top of the ownership semantics sit the rest of Rust’s semantics and they are all checked at compile time by a singular oracular subsystem (i.e. the borrow checker).
The complexity really begins to rise, for me, if ai want to attempt to program without engaging with ownership as the methodology or semantics for handling all of the above mentioned ‘resources’. I prefer, and believe, that a broader set of formalisms should be available for ‘handling’ resources, that those formalisms should exist with parameterized granularity, and that the foundational semantics for those mechanisms should come from type systems’ ability to encode capabilities and conditions for particular types in a program. That position is in contrast to the universal and foundational ownership semantic, especially with the individualistic fixed granularity, that Rust chose.
That being said, it is bordering on insanity to attempt to program in such a ‘style’/paradigm/method in Rust. My preferences make Rust’s chosen focus on ownership seem complex at the outset, and attempts to try and impose an alternate formalism in Rust (which would, by necessity, have to try and be some abstraction over Rust’s ownership semantics which hid those semantics and tried to present a different set of semantics to thenprogrammer) take that complexity to even higher levels.
The real problem with trying to frame my position here as complexity is the following: to me Rust and its ownership semantic is complex because I do not like it’s chosen core semantic construct, so when I think about achieving something using Rust I have to deal with additional semantics, semantic objects, and their constraints on my program that I do not think are fit for purpose. But, if I wanted to program in Rust without trying to circumvent, ignore, or disregard it’s choices as a language and just decided to accept (or embrace) it’s semantic choices the complexity I perceive would decrease significantly and immediately.
For me, Rust’s ownership semantics create an impedance mismatch that at the level of language use FEELS like complexity (and acts like complexity in a lot of ways), but is probably more correctly identified as just what it is… an impedance mismatch, nothing more and nothing less. For me, I just chose not to use Rust to avoid that, but for others they get focused on these issues and don’t actually get to the bottom of their issues and just default to calling it complexity during discussion.
All in all, I am probably being entirely to optimistic about the comments about the complexity of Rust and ownership and most commenters are just fighting to fight, but I genuinely believe there is much to discuss and work through in programming language design theory and writing walls of text on HN helps me do that.
I’m pretty sure there is not any realistically feasible way to ever prove your statement. But I hope a majority of people can recognize the sheer magnitude of C++ as a language and take a position that it may not be possible to master the whole thing. Rust is ‘smaller’ language using some metrics (most metrics really) than C++ is another thing I would hope most people can accept. So, given that the comparisons between the two wholes being a semi-intractable discussion I would propose the following:
When considering some chosen subset of functionality for some specified use case, how do Rust and C++ compare in the ability to ‘master’. There are wide and varied groups (practically infinite) of features, constructs, techniques, and implementations that achieve targeted use cases in both languages, so when constructing a given subset which language grants the most expressivity and capability in the more ‘tight’ (i.e. masterable) package?
I think that’s a way more interesting discussion to have. Obviously, where the specified use case requires Rust’s definition of memory safety to be implemented 100% of the time (excluding a small-ish percentage of delimited ‘unsafe but identifiable’ sections) the Rust subset will be smaller due to the mandatory abstractions required to put C++ anywhere near complete coverage. So it may make sense to allow the subset to be defined as not only constructs in the base language, but include sealed abstractions (philosophically if not in reality) as potential components in the constructed subsets.
I may have to try and formulate some use cases to pose in a longer something to see if any truly experienced devs can lay out their preferred language’s best candidate subset in response. It would also be fascinating to see what abstractions and metaprogramming would be used to implement the subset candidates and figure out how that could factor into an overall measurement of the ‘masterable-ness’ of the given language (i.e. how impossible a task is it to be able to rely on a subject matter expert to implement any proposed subset for any given use case).
Very cool, thanks! I will try this with Mudlet.
Interesting to see Fil‑C used with a large framework like Qt. The fact that it compiles with minimal changes says a lot about the compatibility layer and the InvisiCaps approach.
This is awesome. Would love to see if it catches some of the Qt bugs I found but haven't been resolved yet[1].
[1] https://qt-project.atlassian.net/browse/QTBUG-122658
AFAIK Fil-C does not catch all memory safety bugs, for example some use-after-free are just not bugs but work as intended (you still access the original data/allocation). This means that it's not a sanitizer and code that runs fine on Fil-C may show UB when run normally.
I only tried to get Qt Base up and running. But Qt Declarative will be next, after Qt Base.
That would be awesome (:
eh, I daily-drive a -fsanitize=address -fsanitize=undefined build of Qt and actual memory bugs are almost never a thing - I think the only time I had some were in tooling executables such as qmllint, but not in the framework itself. Most of the bugs by large are more "behaviour" bugs.
I’m impressed that QT runs clean enough under ubsan for daily use.
ubsan definitely has some warnings due to a few "technically UB" patterns used as optimizations in Qt as they are known-working on every target platform under the sun, but yeah, no crashes :)
I'm only using this configuration for the software I develop though (+ libc++ debug mode) as it's painfully slow, but it exercises the Qt codebase in depth.
Depends in which sense you want it to "catch" the bugs. As this readme notes/quotes,
> All memory safety errors are caught as Fil-C panics.
If your problem is a memory-based bug causing a crash, I think this would just... catch the memory-based bug and crash. Like, it'd crash more reliably. On the other hand, if you want to find and debug the problem, that might be a good thing.
Sure, if the memory error is an immediately crashing one like a null per deref, but if is (for example) a memory corruption (e.g. an out of bounds write or a write-after-free) then this would be super helpful in exposing where those are happening at the source.
That’s what “catch” means here. As in, catch it in the act. Tools that make bugs crash more reliably and closer to the source of the problem are extremely valuable.
I thought the point of Fil-C was to be a drop in safe replacement of C. This project existing implies it isn't. What's going on?
It isn't entirely drop-in for all programs.
Most large C code bases aren’t really written in C. They’re written in an almost-C that includes certain extensions and undefined behavior. In this case, it uses inline assembly (an extension) and manipulating pointers as integers (undefined behavior).
While I’m always thankful when people give the broad perspective and context in a discussion, which your comment does. The specifics of this particular project’s usage of almost-C is not something I could have quickly figured out, so thanks. For such a large program, an to be as old as Qt is at this point, I find it impressive and slightly amazing that it has in some sense self-limited its divergence from standard C. It would be interesting to see what something like SQLite includes in its almost-C.
The more portable a project is, the less weird stuff it’s likely to do. The almost-C parts become more of a headache the more OSes and compilers you support. This seem pretty tame, and I’d expect SQLite to be similar. I work on some projects that only support a single OS, compiler, and CPU architecture and it’s full of dependencies on things like the OS’s actual address space (few 64-bit archs use all 64 bits).
It's not done yet.
Sure fooled me. I follow his Twitter account and there isn't much he hasn't got building with it at this point. UX comes later. Amazing it's the random work of one person
The author wrote WebKit’s allocator and worked on JavaScriptCore for over a decade. I really enjoyed his posts on the WebKit blog over the years like this one on the concurrent garbage collector (2017) https://webkit.org/blog/7122/introducing-riptide-webkits-ret...
I don’t think so much is fil-c itself, but from the looks of the diff it’s a new platform essentially. That can require porting existing software generally which you can read from the posted diff
I believe much more in making C/C++ safer than using something as complex as Rust.
SafER is better than deeply complex and unable to be understood except by Rust experts.
In my experience (20+ years with C/C++, and about 4 years with Rust), Rust is significantly less complex than C++, while being similarly capable. The extra syntax that throws off so many C++ devs is almost exclusively about data types and lifetimes, which I find very useful for understanding my own code and others', and which I wish I had in C++.
Great, if you are right everyone is going to be using Rust eventually.
I like the concepts proposed by Rust but do not like fighting with the borrow checker or sprinkling code with box, ref, cell, rc, refcell, etc.
At some point there’s going to be a better designed language that makes these pain point go away.
> I like the concepts proposed by Rust but do not like fighting with the borrow checker or sprinkling code with box, ref, cell, rc, refcell, etc.
I'm not sure why this would be confusing or disliked by a C++ dev.
Rust's Box<T> is similar to C++'s std::unique_ptr<T>.
Rust's Rc<T>, Arc<T>, and Rc<RefCell<T>> serve similar uses to C++'s std::shared_ptr<T>.
Rust's Weak<T> is similar to C++'s std::weak_ptr<T>.
Verbosity of both is nearly identical. The big difference is that Rust enforces the rules around aliasing and mutability at compile time, whereas with C++ I get to find out I've made a mistake when my running code crashes.
> Great, if you are right everyone is going to be using Rust eventually.
Every task does not need speed and safety. Therefore, "everyone" doesn't need Rust.
But I could easily see a future where C++ is relegated to legacy language status. It has already had decades of garbage-collected languages chipping away at most of its general-purpose uses, but Rust seems capable and in a position to take away most of its remaining niches.
It's kind of why the old C++ programmer that I am decided to learn Rust in the first place - seemed like a good idea at the time to skate where the puck is heading.
The real answer should have been a new language that has memory safety without all the extra conceptual changes and orthogonal subsystems that Rust brings. The core value of safety did not need the reinvention of everything else with the accompanying complexity and cognitive load. For example Zig which instead of introducing a new metaprogramkming language, it uses...... Zig - imagine using the same language instead of inventing a new additional language with all the accompanying complexity and cognitive load and problems. Rust is for those who revel in complexity. And traits - traits and extra complexity not needed for safety. And result and option and move by dedfault - none of these things were needed but they all add up to more complexity and unfamiliarity and cognitive load. And when you add it all together and intertwine it you end up with something so unfamiliar that it no longer looks like "ordinary programming" it looks like something from the Cambrian period.
As a C++ developer, my experience with learning both Rust and Zig is that they're both good languages, and any reasonably skilled C++ developer could learn either language if they put their mind to it.
If you forced me to pick between Zig and Rust for a long-running project though, I'd pick Rust 10/10 times for the simple fact that it has been stable for more than a decade and already has momentum and funding behind it. Zig is a cool language - one that I've actually written more of than Rust - but it hasn't hit 1.0 yet and still has significant churn in both the language and standard library.
My point is this:
Rust is complex.
That is not a statement anyone can deny. Unless you're a Rust-bro "hey man I learned it so I’m baffled how you can't see it's super simple ..... etc etc" - often the implication that you're not very smart if you think Rust is complex.
Of everything that I have learned about programming, this is the BIGGEST lesson: - complexity is bad, avoid complexity. Complex is not the same as "sophisticated", which implies necessary intricacy. Complex means unneeded unnecessary cognitive load - things made harder than they should be when it could have been avoided - that's complexity. If you are writing complex code then you're writing bad code. And Rust is complex.
It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
What we needed was Rust-- i.e Rust without all the non-safety related extras that make it different - it's all the non-safety add ons that makes Rust into a Rube Goldberg machine.
> It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
We did, arguably. Those languages are called JavaScript, Python, Java, C#, etc. Those languages tend to be eschewed in certain niches, though, and it's there that simplicity tends to be harder to achieve.
> What we needed was Rust-- i.e Rust without all the non-safety related extras that make it different - it's all the non-safety add ons that makes Rust into a Rube Goldberg machine.
I think it might also be worth considering that some people find those "non-safety related extras" a good thing. Dropping backwards compatibility and/or familiarity, just like everything else, is a tradeoff, and that tradeoff might be worth it if you think the resulting semantics are nicer to work with.
> It is sad that we did not end up with a SIMPLE programming language that solves the memory safety problem.
I am not a savant by any means, and yet I was able to get up and running with Rust relatively quickly. What Rust isn't is ergonomic, in that Rust gets very annoyed with the ways that one might want to structure their code. Trust me, I got bit by the borrow checker countless times, and it did grate on me.
As a result, there are many tasks that I would avoid using Rust for, tasks where both speed and safety aren't critical. But if neither is a priority, the list of alternative languages I can resort to is quite long, much longer than Zig, C++, or C. And in the cases where both are a factor, I would consider being needled by the compiler to be a feature and not a bug.
You'd be surprised to see, after deep inspection, how little of Rust you can remove while keeping its safety story the same (that is, memory safe without GC).
Traits? Nope. We need some way for code reuse. Classes cannot be made memory safe without extra cost (at least, I don't know how can they). And they are not less complex either. Templates like C++? More complex, and doesn't allow defining safety interfaces. No tool for code reuse? That will also severely limit the safety (imagine how safe Rust was if everyone would need to roll their `Vec`).
The borrow checker of course cannot be omitted. ADTs are really required for almost anything Rust does (and also, fantastic on their own). Destructors? Required to prevent use after free.
Async can be removed (and in fact, wasn't there in the beginning) which is a large surface area, but even today it can mostly be avoided if you're not working in some areas.
I don't think anybody can deny Rust is complex, but most often it's inherent complexity (what you call "sophistication") given the constraints Rust operates in, not accidental complexity.
> Rust is complex.
Compared with what? C++? It is not.
False equivalence.
You focused on the C++ aspect and completely failed to engage with the actual critique - what is a “simple” language that you’re evaluating Rust against as a failure?
> The real answer should have been a new language that has memory safety without all the extra conceptual changes and orthogonal subsystems that Rust brings. The core value of safety did not need the reinvention of everything else with the accompanying complexity and cognitive load.
What would the minimal set of features be, in your opinion?
> For example Zig which instead of introducing a new metaprogramkming language, it uses...... Zig - imagine using the same language instead of inventing a new additional language with all the accompanying complexity and cognitive load and problems.
Zig probably isn't the best comparison since Zig doesn't try to achieve the same level of compile-time memory safety guarantees that Rust aims for. For instance, Zig doesn't try to statically prevent use-after-frees or data races.
That being said, as with everything it's a question of tradeoffs. Zig's metaprogramming approach is certainly interesting, but from what I understand it doesn't offer the same set of features as Rust's approach. For example:
- Zig's generics are more similar to C++ templates in that only instantiated functions are fully checked by the compiler. Rust's generics, on the other hand, are completely checked at the definition site so if the definition type-checks the author knows it will type-check for all possible instantiations. Rust's approach also lends itself to nicer error messages since everything a generic needs is visible up front.
- Zig's comptime isn't quite 1:1 with Rust's macros. comptime is for... well, compile-time computation (e.g., reflection, compile-time branching, or instantiating types). Macros are for manipulating syntax (e.g., code generation or adding inline support for other languages). Each has things the other can't do, though to be fair there is overlap in problems they can be used to solve.
In any case, metaprogramming approaches are (mostly?) independent of memory safety.
> And result and option and move by dedfault - none of these things were needed but they all add up to more complexity and unfamiliarity and cognitive load.
I don't think Result/Option are that complex (if at all) since they're trivially derivable from discriminated unions/sum types/enums.
I'm also not sure how move by default is necessarily "more complexity... and cognitive load"? Maybe as a result of unfamiliarity, perhaps, but that seems more a property of a person than a language, no?
There's also https://docs.rs/proc-macro2/latest/proc_macro2/ which effectively allows for the construction of procedural macros with normal Rust possible. As well as enable the use of proc_macro types in normal rust. It'll make it to stable someday, I hope.
> The real answer should have been a new language that has memory safety without all the extra conceptual changes and orthogonal subsystems that Rust brings.
So what you're saying here is that you don't understand that Rust's rules around memory ownership, aliasing, and mutability are what allow the language to provide deterministic compile time memory safety without runtime cost. If you figure out another way to guarantee memory safety at compile time with zero runtime overhead, you should write a paper and start another language around it!
https://en.wikipedia.org/wiki/Capability_Hardware_Enhanced_R... exists, and is an exciting, laudable effort, I think. But requires hardware support as well as language modifications.
I don’t know what you mean by SafER but it’s important to remember that Fil-C sacrifices a lot of performance for that safety which detracts the reasons you’d be running that software as otherwise C was a bad language for them. Sometimes this won’t matter but there are places fil-c won’t be able to go that Rust can - embedded and OS kernel come to mind. Other places would be things like browsers or games. Rust gives you the safety without giving up the ability to do performance.
Also, I could be wrong but I believe any assembly linked into Fil-C bypassed the safety guarantees which would be something to keep in mind (not a big deal generally, but a source of hidden implicit unsafe).
I’m apparently comment happy on this OP, but, the typing of it looks funny because it starts the sentence, I’m pretty sure OP was saying safER, as opposed to SAFE (as in totally safe instead of comparatively safer). I have been quite charitable to OP in some sibling comments and will do so here. I think OP is attempting to give Fil-C some credit for being an attempt to increase the overall memory safety of existing code without incurring the complexity of a new language or the complexity of rewriting long running/widely distributed code. It is a decent sentiment and a viable methodology to achieve a laudable goal, but is certainly susceptible to caveats like the performance penalty you mention.
If you can’t understand ownership I’m baffled how you believe you can write well behaved C or C++.
Rust at least embeds this information in the API with checks. C and C++ are doc comments at best.
I’m going to start this comment by specifying that I don’t know what OP was considering complex about Rust and, unfortunately, a large amount of discussion on the topic tends toward strawman-ing by people looking to argue the ‘anti-Rust’ side of said discussions. Additionally, the lack of a contextual and well considered position against some aspect of Rust, as a language, is very common, and at worst the negative take is really just a overall confrontational stance against Rust’s uptick in usage broadly and its community of users, as perceived (and also strawmanned), in a generally negative light. But since borrowing is not explicitly mentioned by GP, I will give a slightly different position than he might, but I think this is an interesting perspective difference to discuss and not a blatant ad hom argument used to ‘fight’ Rust users on the internet.
From my position the complexity incurred by ownership semantics in Rust does not stem from Rust’s ‘formalization’ and semi-reification of a particular view on ownership as a means of program constraint. The complexity of Rust, in relation to ownership, comes with the lengths I would have to go to design systems using other logical means of handling references (particularly plain hardware implemented pointers) to semantic objects: their creation, specification, and their deletion. Additionally, other means of handling resources (particularly memory acquired via allocation): its acquisition, transport through local and distributed processes (from different cores to over the wire), and its deletion or handing back to OS.
Rust adopts ownership semantics (and value semantics to a large degree) to the maximum extent possible and has enmeshed those semantics throughout all levels of abstraction in the language definition (as far as a singular authoritative ‘definition’ can be said to exist as a non-implementation related formalism). At the level of Rust the language, not merely discussions and discourse about the language, ownership semantics are baked in at a specified granularity and that ownership is compositional over the abstraction mechanisms provided. These semantics dictate how everything from a single variable on the stack to large size allocations in a general heap to non-memory ‘resources’, like files, textures, databases, and general processes, are handled in a program. On top of the ownership semantics sit the rest of Rust’s semantics and they are all checked at compile time by a singular oracular subsystem (i.e. the borrow checker).
The complexity really begins to rise, for me, if ai want to attempt to program without engaging with ownership as the methodology or semantics for handling all of the above mentioned ‘resources’. I prefer, and believe, that a broader set of formalisms should be available for ‘handling’ resources, that those formalisms should exist with parameterized granularity, and that the foundational semantics for those mechanisms should come from type systems’ ability to encode capabilities and conditions for particular types in a program. That position is in contrast to the universal and foundational ownership semantic, especially with the individualistic fixed granularity, that Rust chose.
That being said, it is bordering on insanity to attempt to program in such a ‘style’/paradigm/method in Rust. My preferences make Rust’s chosen focus on ownership seem complex at the outset, and attempts to try and impose an alternate formalism in Rust (which would, by necessity, have to try and be some abstraction over Rust’s ownership semantics which hid those semantics and tried to present a different set of semantics to thenprogrammer) take that complexity to even higher levels.
The real problem with trying to frame my position here as complexity is the following: to me Rust and its ownership semantic is complex because I do not like it’s chosen core semantic construct, so when I think about achieving something using Rust I have to deal with additional semantics, semantic objects, and their constraints on my program that I do not think are fit for purpose. But, if I wanted to program in Rust without trying to circumvent, ignore, or disregard it’s choices as a language and just decided to accept (or embrace) it’s semantic choices the complexity I perceive would decrease significantly and immediately.
For me, Rust’s ownership semantics create an impedance mismatch that at the level of language use FEELS like complexity (and acts like complexity in a lot of ways), but is probably more correctly identified as just what it is… an impedance mismatch, nothing more and nothing less. For me, I just chose not to use Rust to avoid that, but for others they get focused on these issues and don’t actually get to the bottom of their issues and just default to calling it complexity during discussion.
All in all, I am probably being entirely to optimistic about the comments about the complexity of Rust and ownership and most commenters are just fighting to fight, but I genuinely believe there is much to discuss and work through in programming language design theory and writing walls of text on HN helps me do that.
Rust is a much easier language to master than C++.
I’m pretty sure there is not any realistically feasible way to ever prove your statement. But I hope a majority of people can recognize the sheer magnitude of C++ as a language and take a position that it may not be possible to master the whole thing. Rust is ‘smaller’ language using some metrics (most metrics really) than C++ is another thing I would hope most people can accept. So, given that the comparisons between the two wholes being a semi-intractable discussion I would propose the following:
When considering some chosen subset of functionality for some specified use case, how do Rust and C++ compare in the ability to ‘master’. There are wide and varied groups (practically infinite) of features, constructs, techniques, and implementations that achieve targeted use cases in both languages, so when constructing a given subset which language grants the most expressivity and capability in the more ‘tight’ (i.e. masterable) package?
I think that’s a way more interesting discussion to have. Obviously, where the specified use case requires Rust’s definition of memory safety to be implemented 100% of the time (excluding a small-ish percentage of delimited ‘unsafe but identifiable’ sections) the Rust subset will be smaller due to the mandatory abstractions required to put C++ anywhere near complete coverage. So it may make sense to allow the subset to be defined as not only constructs in the base language, but include sealed abstractions (philosophically if not in reality) as potential components in the constructed subsets.
I may have to try and formulate some use cases to pose in a longer something to see if any truly experienced devs can lay out their preferred language’s best candidate subset in response. It would also be fascinating to see what abstractions and metaprogramming would be used to implement the subset candidates and figure out how that could factor into an overall measurement of the ‘masterable-ness’ of the given language (i.e. how impossible a task is it to be able to rely on a subject matter expert to implement any proposed subset for any given use case).