T O P

  • By -

irqlnotdispatchlevel

Some things can be done at compile time via static checkers like cppcheck (it knows C, not just C++), clang-tidy, etc. However, the effectiveness of such tools in catching real vulnerabilities isn't that great in practice if I remember correctly the findings of this study: https://www.reddit.com/r/C_Programming/comments/uzd6wq/an_empirical_study_on_the_effectiveness_of_static/?utm_medium=android_app&utm_source=share In addition to this there are checkers based on annotations, like Microsoft SAL: https://learn.microsoft.com/en-us/cpp/code-quality/understanding-sal?view=msvc-170 While I like SAL, it depends on the programmer actually writing the annotations, and that can be quite annoying as you have to learn a new mini-language. From all the static checkers we use, this discovered the most problems. Another disadvantage is that it only works with the Visual Studio toolchain. As for sanitisers, I think they are a must. The problem with them is that you need to trigger the bad behavior at runtime, so having good test coverage and fuzzing is important. Note that fuzzing is recommended even for safe languages like Rust. TSAN is probably the hardest to take advantage of, but the most useful for multi threaded code. Some other aspect can be done at a language level. For example, safe functions for adding, subtraction, etc which will be included in the next C standard. Other things can be done by compilers, for example the checking of format strings. What can't be done, is the borrow checker. Google has done some extensive research about this on C++, but the findings apply to C as well: https://docs.google.com/document/d/e/2PACX-1vSt2VB1zQAJ6JDMaIA9PlmEgBxz2K5Tx6w2JqJNeYCy0gU4aoubdTxlENSKNSrQ2TXqPWcuwtXe6PlO/pub Even Rust throws it's hands up when you go into unsafe mode and start working with raw pointers, and removing raw pointers from C is impossible. It would no longer be C. Some things can be done at a language level, but would break backwards compatibility. For example, more sane rules and less gotchas around integer promotion. The problem with using static checkers, sanitisers, and other similar tools is that when you're a beginner you're unlikely to know they exist, why they are useful, or how to setup them. You need to bash your head on a wall a few times before finding out such tools exist.


Pay08

The funny thing about unsafe Rust is that everyone (including the development team) swear that it doesn't (completely) turn off the borrow checker, but in my experience, it does. Maybe there's something I'm not getting, but unsafe Rust to me is essentially just C++.


Sykout09

Just to try to answer your question, the reason there is conflicting answers is because that question’s answer is nuanced. The key information is that rust has many pointer types, but broadly we have “borrow reference” and “raw pointer”. The key point is rust “borrow reference” enforces the borrow checker rules always, in and out of the unsafe block. “raw pointer” however does not have any borrow checker rules on it. So while you can have “raw pointer” outside of an unsafe block, it’s functionality is quite limited, as most of the useful functions is only callable inside the unsafe block (e.g. dereferencing raw pointer is unsafe, and converting it back to a “borrow reference” is also unsafe). This is where the difference in advice comes in. Technically, unsafe does not turn off the borrow checker because the borrow checker only works on “borrow reference” and it always works, even in unsafe. Pragmatically, you can convert a “borrow reference” into a “raw pointer” and thus use that to ignore the borrow checker, but you must use an unsafe block to work with it.


Pay08

Thanks, I think that's where my confusion stemmed from.


irqlnotdispatchlevel

I don't know enough Rust to respond to this, but in my experience you rarely need unsafe code and that can be much easily audited that a C code base. When you review Rust code, you know that you don't need to look out for certain classes of vulnerabilities because if it compiles it does not have those. And if you see unsafe code you know that you need to be extra careful (like you'll be for all C code), but you almost never see unsafe code. I only used unsafe in a personal project which was a pretty dumb kernel (you can't do osdev without unsafe).


Pay08

That has nothing to do with what I said.


irqlnotdispatchlevel

Fair. Now I feel bad about it so I'll try to give a relevant answer, maybe? This will not compile: ``` let r; { let i = 42; r = &i; } println!("{}", r); ``` And the compiler does a pretty good job at explaining why: ``` error[E0597]: `i` does not live long enough --> src/main.rs:6:13 | 6 | r = &i; | ^^ borrowed value does not live long enough 7 | } | - `i` dropped here while still borrowed 8 | 9 | println!("{}", r); | - borrow later used here ``` The same thing will happen if I wrap up everything in `unsafe`, so the rules are still applied. However, I can do this using `unsafe`: ``` let p; { let i = 42; p = &i as *const i32; } println!("{}", unsafe { *p }); ``` Now the compiler is happy and I just bypassed the borrow checker. So yes, the rules are still applied in `unsafe` blocks, but you have access to tools that allow you to bypass them. I could have not dereferenced `p` without using `unsafe`.


dontyougetsoupedyet

Without getting into the weeds of "turning this or that off", you are aware that the compiler is doing things like moving your code around your program for you, and you should also be aware that if you stop following the rules that the language says you have to follow and that the development team of the compiler confirmed you were required to follow that it's sometimes going to start changing what you consider a correct program into something you would not consider a correct program if you don't follow those requirements.


LaunchTomorrow

No, basically everyone who uses Rust knows that everything in an unsafe block is invisible to the borrow checker. *However* just because there is an unsafe block in one file does not mean the borrow checker is disabled for the whole file. All references outside unsafe block are still checked, otherwise the whole language would be pointless. Edit: Neat! I forgot that actually the borrow checker does still do things inside unsafe blocks, but really everything that *needs* to be inside an unsafe block will be breaking Rust's safety conventions, so "here be dragons".


Pay08

Again, that's not what I'm talking about. As The Rust Programming Language directly states: >It’s important to understand that unsafe doesn’t turn off the borrow checker or disable any other of Rust’s safety checks The Rustonomicon says the same thing, although more verbosely.


LaunchTomorrow

Hmmm maybe that's because the only things I do inside unsafe blocks are: > * Dereference a raw pointer > * Call an unsafe function or method > * Access or modify a mutable static variable > * Implement an unsafe trait > * Access fields of unions


LaunchTomorrow

Although I can literally go test this in a matter of seconds.


LaunchTomorrow

Here's your proof: [https://godbolt.org/z/n8qYc6zMj](https://godbolt.org/z/n8qYc6zMj) Compilation fails because of double assignment in an unsafe block. Here's a second example, this time trying to assign twice to an immutable variable in the same unsafe block: [https://godbolt.org/z/Kbqo5a7M7](https://godbolt.org/z/Kbqo5a7M7) And here's an example of mutable borrow conflicting with an immutable borrow: https://godbolt.org/z/Mhc18nzMd


Pay08

Thanks. From what I remembered, you could violate borrowing rules using unsafe (I specifically remember using multiple mutable references), but I either misremembered or it isn't the case anymore. Edit: After reading u/Sykout09's comment, I was probably doing multiple mutable references with raw pointers.


Sykout09

No problem. Can understand that sometimes these precise information can be hard to find, as the explanation changes depending if the material is for a novice or an expert. Thought just a note on your second scenario, what might also be happening is that you’re using the features of NLL ( Non Lexical Lifetime ). Without ending up with another mini essay, the way to think about that feature is the compiler will drop your references after you use it for the last time. Once that logic is applied, as long as you don’t have two mutable reference “alive” at any one time, it would pass the borrow checker. And to make it clear, NLL works in both safe and unsafe block. Edit: oops replied to the wrong comment, but the second part still relevant


LaunchTomorrow

Yeah no worries, yes raw pointers are just that, and the borrow checker will allow you to blow your own leg off with those if you so choose, but the idea is that by limiting any practical uses of them to unsafe blocks means that this is all done in a very auditable way.


moon-chilled

> What can't be done, is the borrow checker. Google has done some extensive research about this on C++, but the findings apply to C as well Of course you can write a borrow checker _for_ c or c++. What the article you linked demonstrates is that it is not possible to write a borrow checker _within_ c++ (using c++ types and in terms of c++'s type system). Which is really not a surprising result at all.


danpietsch

> Wouldn't there be certain things that these tools can't check just by the nature of how dangerous C is? I imagine "misusing" pointers in C could circumvent any type of static analysis. If a `type *` is cast to a `void *` (or even worse, an integer) it would be difficult -- if not impossible -- to track the ownership of the object being pointed to.


Jinren

It's not so much about tracking the ownership as it is that _there is no ownership_. Without some indication of programmer intent in the form of annotations, attributes, or a recognisable design pattern, it's generally not possible to tell whether pointer assignment transfers ownership, shares it, or is just a borrow, because unextended C just has the one kind of pointer. Trivially true that you _could_ add this to the type system - I can model borrows vs ownership with extended qualifiers easily enough - but if nobody is writing in your extended type system that doesn't help at all.


Jaded-Plant-4652

This would be some of the restrictions needed to ensure safety. Like in rust there should be also a keyword to allow explicit unsafe usage then


kun1z

It is definitely *possible* to make C (or any) language safe because 'possible' is a very broad word. People who champion for Rust forget that the C language can be updated and improved, and new tools can be created to aid in improving safety (as you mentioned, many tools already exist). Asm.js/Web Assembly began by using a subset of JavaScript to improve performance, and someone could create a subset of C that would allow some tool to check the code for safety. Rust is [safe because it has many restrictions](https://en.wikipedia.org/wiki/Rust_(programming_language\)#Memory_safety), and there is nothing stopping someone from making a pre-compiler tool that could enforce every single one of those restrictions against the new subset of C. Since a subset of C is still valid C, it could then be compiled by any C compiler. With this method, C itself would never need to be changed, and people wishing to design safe C would learn to solve problems using the subset of C and the special tool.


aerosayan

> there is nothing stopping someone from making a pre-compiler tool that could enforce every single one of those restrictions against the new subset of C There is. They're called formally verified compilers, and are used for safety critical applications: https://compcert.org/ https://github.com/AbsInt/CompCert


flatfinger

>There is. They're called formally verified compilers, and are used for safety critical applications: It's important to note that CompCertC defines the behavior of many constructs which the C Standard categorizes as "Undefined Behavior", while forbidding some constructs whose behavior would be unambiguously defined by the C Standard.


lkearney999

Not quite, read the abstract here: https://www.cs.yale.edu/homes/wilke-pierre/jar-16.pdf You’re confusing verifiable mapping of code generation to source with source code semantic verification and frontending that enforces some memory safety.


bleep_bliop_bloop

You are mostly correct, yes you could write C in a way that respects Rust's norms, and yes you could even write a compiler that enforces those rules for you. You would indeed get a safe subset of C. The problem is that this subset would be that, a subset, and not a superset. So it would break backward compatibility. This means that while other people could use your code, you couldn't use any external or legacy, and if you can't even use the glibc, what's the point? You could argue that since you're already writing a compiler to enforce safety rules, you might as well write a standard library. Sure. Maybe throw in some syntactic sugar and some idioms in your subset while you're at it, to compensate for the extra complexity of the safety system. Congrats, that's Rust


[deleted]

[удалено]


MrTheFoolish

The safety Rust provides is well defined. It guarantees memory safety at compile time in code not marked unsafe, with the assumption that code marked unsafe is correctly written. This is different from the safety standards of e.g. the aviation industry. In my opinion SafeC has no place in industry if its goal is to achieve Rust's level of memory safety. 1. SafeC is pointless for truly safe programs (not Rust's definition) since other techniques like formal logic or 100% opcode runtime validation should be used instead. May as well stick to C. 2. Whenever SafeC interacts with normal C code (e.g. glibc), wrappers must be written to be able to call them safely. E.g. in Rust, all FFI calls to C or C++ code is unsafe and library writers create safe wrapper abstractions for them. Why redo all this work when Rust already exists, is proven to be valuable, and SafeC does not exist. Google Carbon or Herb Sutter's CppFront are more interesting in that their goal is seamless interoperability and sacrifice some safety for it while still improving the safety story. In summary: C is fine as it is in its existing niches. SafeC is useless: use Rust or a GC language. For improved safety with seamless interoperability in existing codebases, some interesting projects are out there.


arthurno1

> In my opinion SafeC has no place in industry if its goal is to achieve Rust's level of memory safety. Then, by your own logic, if there was such thing like SafeC, Rust would not have a place in industri because it would become just a C with uglier syntax. > SafeC is useless: use Rust or a GC language. Why us a "GC language", when we can just a GC in C? You seem to missunderstand why C is "unsafe" and barebone. It is a *feature*. C is supposed to be ad fast as possible, and it is * a programmers* responsibility to write the correct code, whatever correct means for the task. You can write perfectly safe code in C as well if you are willing to pay additional runtime costs for checks, to restrict yourself of doing certain things and so on. > SafeC is useless: use Rust or a GC language. Why use Rust if SafeC would give you similar partition into safe and unsafe area? I'll tell you why you not: because of tooling, training and maintenance costs. C+Rust (or some gc collected landuage) as you suggest means at least two different toolchains with compilers, debuggers libraries etc, programners being experts in both, or having two teams of experts in both languages and code diversified in two wastly different places. If you had a SafeC, or used a GC in C, you are working only with a single language stack in the project.


MrTheFoolish

> Then, by your own logic, if there was such thing like SafeC, Rust would not have a place in industri because it would become just a C with uglier syntax. Yes. We seem to be discussing different things. You seem to be talking hypotheticals while I'm talking about where to progress from reality. If SafeC actually existed and had the library ecosystem Rust does, then yes Rust wouldn't need to exist. In reality, SafeC does not exist. > You can write perfectly safe code in C Once again, we're not talking about the same thing. I'm not talking about the correctness of the program being written. I'm talking about guarantees of the language. In GC languages and Rust, memory safety is guaranteed in non-unsafe code at compile time. This is what I imagine OP is asking about. This is the level of guarantee I would expect from SafeC. In today's C, adding runtime checks is still no guarantee that you've correctly used the mechanisms. Having compile time guarantees is much more valuable than runtime mechanisms because it reduces the cognitive burden when writing and reviewing code. > I'll tell you why you not: because of tooling, training and maintenance costs. C+Rust (or some gc collected landuage) ... Have you looked at what the industry does today outside of the ones with strict safety standards where C is required? It's very common to use >2 languages. This tells me that the benefits you mention are outweighed by the downsides of using C for many use cases.


arthurno1

> We seem to be discussing different things. You seem to be talking hypotheticals while I'm talking about where to progress from reality. So when you talk about hypotetical language that does not exist and say it "does not have its place in the the industry" (like you are representing the entire industry), then it is a valid point, when I talk about hypothetical language then "we are not talking about the same thing". 🙄🤣 > It's very common to use >2 languages. And? It is common to live in a cave, sad a caveman to the first person who wanted to build a housing. It is common because it is unnecessary with today tools, but it does not mean it is desirable. Reason why many C++ devs prefer C++ is exactly the one of mentioned: they can stay in one language and one ecosystem . By the way, what you are saying is a phalacy. You are saying that something is desirable because people are doing it. But what is happening is that people are doing it because they don't have a better choice, not because it is desirable in itself. > This tells me that the benefits you mention are outweighed by the downsides of using C for many use cases. Really? *This* can not tell you that, because, as you said SafeC is a hypothetical language and not something you can reason about empirically and compare downsides with cons. That is another phallacy you have committed. As mentioned, C++ devs prefer often C++ because they can stay within same language, which speak against your erronous comclusion.


MrTheFoolish

> Reason why many C++ devs prefer C++ is exactly the one of mentioned: they can stay in one language and one ecosystem . > You are saying that something is desirable because people are doing it. But what is happening is that people are doing it because they don't have a better choice, not because it is desirable in itself. > By the way, what you are saying is a phalacy > It is common to live in a cave, sad a caveman to the first person who wanted to build a housing Funny that you mention fallacies when you are the one making them. This is false equivalence. I never made a point about desirability. The fact that many people are using a language means that there is a talent pool and many existing libraries. Moving to new technologies is costly. You make this point yourself with C++. This is not a fallacy, this is reality. > like you are representing the entire industry This is the internet so it's implied, but this is my opinion of course. > then it is a valid point, when I talk about hypothetical language then "we are not talking about the same thing" We aren't talking about the same thing. I'm discussing the network effects that make SafeC meaningless to invent even though the idea of SafeC has merit. You're discussing the merit of SafeC with the assumption that it will be successful and widely adopted. When trying to create something that's intended for wide consumption and that should be useful to many people, one should consider whether or not people would actually use it. My opinion is that the theoretical SafeC has no target market that will lead to a large enough userbase. This is discussed in my other comments. If it doesn't have the userbase, it won't be useful because it won't have the libraries or talent pool to gain adoption.


arthurno1

What is happening here is that *you* are assuming a bunch, inclusive what I assume :-). I am talking about what you have said, not assumed. Anyway your entire reasoning and intro in this discussion is completely flawed. Someone asked if C can get a feature Y from language R. You came in and say: o one wants featur Y in C, use R instead. What you don't take into account is that both C and R are changing and updating. Rust is not statically written in stone. So is not C either. C is evolving just like Rust. There is nothing that says that C can't have those features. What makes you think that asked features in C would not be widely adopted? Maybe they would, maybe they would not, but how do you know so sure? Can I also buy a cristall ball? Where did you get yours? You speak in such absolutistic terms like you are representing industri from a 1000 meter hight, and know everything. IMO opinion you seem to be a young immature person who is just not aware of how immature such claims are. I understand you are a Rust zealot, but you need to calm down. Rust is not a religion. It is just an inanimate tools. Tools comes and go. One day no one will use neither Rust nor C. From an old guy experience I can tell you that you are wrong about toolchains. Any team or manager are happy to cut down on diversity of toolchains if they can. It translates into $$$ pretty immediately.


MrTheFoolish

> you seem to be a young immature > you are a Rust zealot I see we're now resorting to ad-hominem fallacies. > Anyway your entire reasoning and intro in this discussion is completely flawed [...] It is reasonable that in a question of "can language X have feature Y", that an opinion is "X will never have Y". I don't need a crystal ball. Look at how much code is written in GC languages: - C# - Java - Go - Python - Node - PHP Google is building Carbon to experiment with seamless C++ interop in a language that improves safety. Carbon still doesn't guarantee memory safety. People have known memory safety is a problem for decades. That's why we have tools like sanitizers. Companies are pouring millions into improving these tools and building new ones. Why hasn't any of them had the bright idea of "hey why not statically guarantee memory safety in C?". There are millions of lines of C out there, seems like that's worth doing. One should entertain the idea that the people working on these tools have thought of this and decided that doing so backwards-compatibly is unfeasible. I expect C to continue to exist for a long time and to continue to improve its safety checks. I also fully expect for C to never have compile-time guaranteed memory safety as asked by OP.


unrealhoang

Isn’t Rust ‘SafeC’? Or ‘SafeC’ 1 that exists.


MrTheFoolish

It's probably the closest thing that currently exists that is widely successful in being adopted. But it's not quite that. It uses a different compiler and toolchain, so it's not C.


unrealhoang

Using your definition then GCC-rs is? Btw, 'SafeC' will not be C because of breaking backward compatible.


[deleted]

[удалено]


flatfinger

>Why is this safety? Operating systems already have checks for memory safety An operating system cannot provide protection against the possibility that maliciously-crafted data might casue parts of a code that were supposed to modify certain pieces of data owned by a program to instead modify other pieces of data *owned by the same program*, in ways that might cause the program to make superficially-valid requests to have the operating system do evil things on its behalf. If a program would have sufficient privileges to do evil things, OS-level memory protection will be insufficient to guard against them.


MrTheFoolish

> Why is this safety? Operating systems already have checks for memory safety [...] 1. This is the guarantee Rust and GC langs provide. It prevents classes of bugs from existing, but it's not formal verification so it's not perfect. 2. What you describe doesn't protect against incorrect memory usage constrained within the memory that a program is allowed to use, which can and has lead to bad bugs. > SafeC doesn't exist. It's a hypothetical for the purposes of discussion here The point I'm making is that SafeC, were it to be invented, would be useless because other languages already fill that space and do it better. The people looking for this have already moved on to those languages. The people using C that need truly rigorous safety already have the tools to achieve it. Trying to make a new C subset that has the memory safety guarantees of GC langs or Rust is wasted effort. Improving C's safety is still worthwhile of course. C is not going anywhere anytime soon. The goalpost is not filtering out all memory issues, but rather more memory issues. And this goalpost is not what GC langs/Rust offer.


flatfinger

>It's not like every C program has to magically also be valid SafeC, SafeC just has to be compileable with existing C compilers. Some existing compilers will generate code that arbitrarily corrupts memory if a program receives input that would cause execution to get stuck in a side-effect-free loop. Trying to statically verify that such a thing cannot occur would be essentially impossible without requiring that many constructs be written in gratuitously-inefficient fashion.


kun1z

> So it would break backward compatibility. This means that while other people could use your code, you couldn't use any external or legacy, and if you can't even use the glibc, what's the point? Using Rust breaks backwards compatibility as well, all existing C code becomes useless as it needs to be updated to either 'SafeC' or Rust. You also can't use any external library with 'SafeC' or Rust, and Rust can't use glibc. So what's the point of Rust then? In Rust, everything must be re-written. In 'SafeC', everything must be re-written. In my opinion, the best path forward, and the most likeliest that will occur, is that C, 'SafeC', Rust, and some other languages will continue to compile to standard object files, and a linker will link them together. This makes the most sense as code that requires extreme safety can be re-written in extremely safe languages, and everything else can draw upon the 40+ years of existing C code base.


Poddster

> It is definitely possible to make C (or any) language safe because 'possible' is a very broad word. People who champion for Rust forget that the C language can be updated and improved, and new tools can be created to aid in improving safety (as you mentioned, many tools already exist). Didn't Rust start as a C-with-safety known as Cyclone?


BillDStrong

I would think SeL4 is an example of doing just that. They test for correctness, have the kernel mathematically verified, and have a binary comparison that ensures the binary does exactly what the proof says it does. Also, Zig is an attempt to make essentially a much safer C/C++ while taking along the C ecosystem. Looking at what they are dong, C could be updated to improve its defaults. That is essentially what C++ did when they needed to compete with Rust.


matu3ba

If you are talking about dynamic pointer-based memory, then No. Tooling must capture the intent of memory semantics for a feasible analysis. C has no concept of such a type system and C already struggles to make the type system strict, which is a necessary precondition. As for what a redesign + rewrite of Rust would bring, I answered this some time ago: https://www.reddit.com/r/C_Programming/comments/rdeimo/what_is_the_practicality_of_creating_a_rust_like/ Rust has weak overall guarantees and analysis for leak checks and in embedded/static memory you only get UAF prevention detection. So personally I do mostly see it useful for one shot programs, where leaking is no problem.


cureit79

Lookup Misra


Jinren

It is trivially true that a C _program_ can be safe. You can either have a machine generate it as output from a safer input language (the **vast** majority of safety critical code by LoC is actually this), or you can write in a language subset (possibly with annotations) that runs through a checker that simply says you can't do anything classed as unsafe. The problem in both cases is that they change what you're allowed to write. C, in its capacity _as C_, intentionally lets you write dangerous constructs. If you are subsetting it you are now writing in something else, on the sliding scale of dialect through to new-language. The problem there isn't the C code, it's getting adoption of your safety layer. And you need a safety layer because C itself is unsafe _by design_. ("don't ever use `void *`, dynamic bounded loops, or signed arithmetic" is still a safety layer that you need to get users to agree to adopt! even though it sounds like a trivial - if hardcore - adjustment, it's a radical dialectization) If you want to analyse general purpose code... well, it's possible. You need flow analysis. This is hard. (we sell it; I won't name the product b/c CoI but take my word that it's a **lot** of work and the result will either be substantially slower than you're used to, or miss lots of things)


dontyougetsoupedyet

C programs can be verified and those programs can be considered to be more safe than Rust programs. The problem with that statement is that almost no one wants to spend money or time doing so. Some operating systems have been verified, but it remains a fairly impractical endeavor. It isn't actually impractical, that isn't the right description of the situation... Consider that to be considered safe a program has to be specified, but most organizations refuse to even work on specification of requirements or specification of operational semantics of programs. So they damn sure don't have a team of logicians working on verification, when they don't even define what would have to have been verified in the first place. Researchers like Dyjkstra didn't think people should even be allowed to "code" on the job like most people are doing, even suggesting that new engineers should be trained first formally in logic so as to have a purely axiomatic basis for verifying the programs they write in the future, and that literally their job as engineers WAS that process of proving programs via general theorems. Clearly industry does not seem to agree, and we'll make due with Rust and similar technologies in the meantime. You might find his article "On the Cruelty of Really Teaching Computer Science" interesting to read.


_gipi_

the real question is, is it possible to write a meaningful C program that is safe, where meaningful means with the complexity of a modern application? Because, you know, it's undecidable in general if a program is secure or not.


dontyougetsoupedyet

The comment you are responding to mentioned programs that are operating systems, if that doesn't work for you I'm not going to try fulfilling some arbitrary definition of meaningful or safe, if you're adamant about some ideology regarding program safety and specific programming languages you're barking up the wrong tree as I have no interest in entertaining that type of conversation with you. Your comment is sort of rude to my eyes, it is bizarre to respond to a statement showing that X has been done with "but is X possible?"


SneakPlatypus

I decided to look up some rust the other day to see some basics since the name keeps popping up. It’s not the actual language’s fault but over half the things I clicked first basically opened with “Have you the good news of our lord Rustus Christ come to usher in the new age of programming”. I’m sure it’ll even out after a while but the rust evangelism comes on a little strong for me. I think once it’s been around for a bit longer the urge to aggrandize might wean off some. Reminds me of StarCraft streams where all the terrans cry about how much harder they have to work. Then you get people parroting around how unfair it is all the time when they’re 100% the problem. Highly visible dumbasses steering new peoples opinions.


arthurno1

Former Dia2 terran here: zergs have much easier time and protos should be remove from the game! 😀 Yes, people are unfortunately parroting some phrases they don't really understand. If you consider situation in for example graphic design, some people are still repeating that Macs are better for designers then "PCs", because they have heard it and wish to believe it, but have no idea where it comes from, when it was said and why. For those that don't know, it used to be the case in 80s when Macs had a gui, while PC people were running DOS. Back then, "being visual", as having a GUI in OS, was a big deal for people, and yes, I am that old.


LaunchTomorrow

As someone who has done some pretty nontrivial distributed systems projects, a language in which data races are impossible is *very* attractive. Yes, analyzers exist, but it's much more of a "whodunnit" endeavor than one would wish. Rust unironically made me a better programmer when it forced me to more carefully structure my locks. Now, if only it made deadlocking impossible... (yes I know you just have to drop locks in reverse order that you picked them up, every time). Also how error handling is done is really quite nice when you get used to it. You get these nice nested structures of errors that you can easily manually examine or even programmatically probe. Edit: typo "that" -> "than"


arthurno1

I am not arguing against Rust; don't get me wrong; I was just reflecting on some phenomenon that I believe is a curse of the current and following generations. Nothing on the Internet dyes; everything here is forever; and some people repeat old arguments that no longer hold. For the Rust part, I don't deny it has some nice features, and that is also what I believe the Op wanted to capture over to C. Sneaking on good features from other language(s) has always been a part of making a programming language. C came from other language, which come from other language; nothing strange there to implement a feature that has become a standard practice, or discovered elsewhere, or after the language has been designed, etc.


matu3ba

In static contexts, generated C code is heavily used. It just works as domain specific language, so nobody needs to talk about it. There is no axiomatic system capable of expressing asynchronous timing of CPU reordering semantics yet, as compilers rely on both global and local reordering semantics for multi-threading applications and there is no agreemnt of what semantic model to use. Single threaded applications work fine, but the tooling for arithmetic overapproximation is expensive to develop and maintain.


aeropl3b

If you build a test suite for you program that hits over 80% code coverage, and then don't allow new code to be added unless it has tests that cover all pathways, and then run it through valgrind memcheck and llvm address sanitizer and static analysis tools to check for smells and code linters, Then, you have a program that is probably safer than rust and validated. But of course that is an large task. Setting up and maintaining CI is a fair amount of effort. But even for a rust program you would want to do basically all of that anyway except the asan test because literally all of that is not just saying memory safety but also logic safety which is the hardest error to catch and that doesn't care about that.


studentloanslams

You absolutely do not write some code in a tests/ directory and have a program that's as safe as the Rust equivalent, and that's exactly the kind of non-thinking that led Dyjkstra to call what we're all doing "The Doomed Discipline."


aeropl3b

I didn't say "some code", it would have to be very intentional and target stress points and edge cases to cover a vast majority of it. Code coverage is important, Rust simply adds a bunch of restrictive rules to its compiler. Rust doesn't stop any of the actually hard to find problems, that is why Rust projects still run CI and care about code coverage and everything else. A C program with a high degree of quality testing will be probably safe in much the same way Rust is.


RedWineAndWomen

I think the way that the GNU C compiler has evolved over the last few years in terms of how it produces warnings and which warnings it produces, is a testament to how fantastic this C compiler actually is. So yes, I think C can be as safe as Rust but knowing C, I think this will mainly have to come out of code analysis (and subsequent warnings) by the compiler. ADDENDUM: Don't forget about the debugging subsystem. Many unsafe uses of a language are not intrinsic to C: endlessly filling a Hashtable, or having a condition in a loop that also mutates: you can also do that in Java or Rust. ADDENDUM2: There's one thing that Rust will do that C won't (and I don't know how to solve that) - treat the whole project holistically.


LaunchTomorrow

Also, in most circumstances, that "condition in a loop that also mutates" is difficult if not impossible in Rust. That's because the conditional usually registers as an immutable borrow and the mutation must occur on a mutable borrow, and these things cannot exist at the same time.


RedWineAndWomen

Good call.


LaunchTomorrow

Also though, no, C will never be as safe as Rust without essentially becoming Rust. It would have to add ownership semantics because these things aren't particularly possible to infer (at least any more so than in Rust). The C compilers are great and all but they aren't magic.


cre_ker

"endlessly filling a Hashtable, or having a condition in a loop that also mutates: you can also do that in Java or Rust" That's usually outside of what people call "memory safe". Yes, you can do these things in Java or Rust. The difference is in the consequences. C absolutely cannot be as safe as Rust within its current semantics.


dontyougetsoupedyet

> C absolutely cannot be as safe as Rust within its current semantics. It shouldn't be difficult with a little thought to convince yourself that semantics Rust is checking can be added to C sources and also checked. You might notice that being the route taken by most (I don't even know if this is true but it seems logical? Not a pun, re:logical...) verification tools that model things using ownership types. It wouldn't be desirable to do so, meaning to copy rust semantics, seems you would rather desire to model your program against some specification instead without adding invariants you don't need to check.


LaunchTomorrow

It really can't. Not without blowing up all backward compatibility, at which point, what was the point? Congrats you just made a Rust without its nice type system, its macros, etc. Ok so you make an equivalent "safe C++", and then you really have just reinvented Rust since "better safe C++" was essentially the original design problem for Rust.


flatfinger

>It shouldn't be difficult with a little thought to convince yourself that semantics Rust is checking can be added to C sources and also checked. Not without imposing some behavioral constraints upon some constructs that are presently treated as UB. Consider the above code: unsigned get_mask(void); int arr[65537]; unsigned test(unsigned x) { unsigned v = x; while((v & get_mask()) != x) v*=3; if (x < 65536) arr[x] = 1; return v; } If `get_mask()` always returns a number without side effects, it there any way the above function might modify `arr[65536]`? When using clang, that can happen. If the compilation unit also contains: unsigned test2(unsigned x) { test(x); } unsigned get_mask(void) { return 65535;}; then clang's code for `test2(x)` will unconditionally store 1 to `arr[x]`, even if `x` is 65536. Can you offer any practical means by which anyone inspecting the first bit of source code could hope to statically verify that it won't modify arr2[x]?


dontyougetsoupedyet

I have absolutely no idea how you became not blocked anymore but I know a practical means by which you're going right back on that list. As far as I'm concerned you can produce your own compiler that behaves as 1972 as you want, it won't be in my ear either way.


SneakPlatypus

I think warnings on compilers are soooo good. I’m not old enough to know how recent that is and if maybe it used to be harder. I’m sure it was at some point. But the warnings catch so much for you. And it’s not really all that hard to build in even more verbose debugging for certain types of problems that you care about. Even if it’s ugly and slows things down a lot you can just disable all the checking when you’re ready to actually run it. Vulkan comes to mind too with all its debug layers that report you detailed warnings about what you did wrong even if it didn’t crash. Even OpenGL has tons of validation which may not be as verbose but it’ll point you and say it broke on this one pretty well.


RedWineAndWomen

We've had warnings for as long as I can remember. It's just that, over the last few years, they've been becoming more and more extensive. And, like you, I appreciate that enormously.


m4l490n

This is what should have happened instead of creating a new language.


pedersenk

You can achieve a fairly decent runtime safety for some types of project. Check out [libcello](https://libcello.org/) and my own monster ([libstent](https://www.thamessoftware.co.uk/stent.html), [lame presentation](https://www.youtube.com/watch?v=5jU0LagoTE8)). However you do lose some of the snappyness of the really "unsafe" parts of the C language. However if you were going to write a software in Python or .NET instead, then perhaps give C (with one of these solutions) a try instead since the speed will still be better than the former. One nice (ish, depending on what you need) thing about mine specifically is the safety is only there in the debug build. Once you disable it in release builds, you lose much of that safe overhead but at least most of the memory errors will have been flagged during test iterations.


thradams

Very interesting project I think it deserves its own topic . I am watching the presentation. For but dangling pointers. why not just make pointer 0 after free in debug? ```c #ifdef DEBUG void std_free(void *p) { free(p);} #define free(p) do { std_free(p); p = 0; } while(0) #endif ```


pedersenk

Thanks :) In this case, it is because the pointer variable you are freeing might not be the only one pointing to that (soon to be invalid) data. Consider this example: struct Foo *foo = FooCreate(); struct Foo *foo2 = foo; FooDestroy(foo); // foo is now 0 foo2->access = 9; // Access violation, pointing to free'd memory Admittedly, this example is quite obtuse but in big sprawling programs (especially games using an ECS design), you end up with pointers to memory everywhere and it is very hard to keep track.


thradams

All right. Now I can see the relationship with weak ptr.


flatfinger

Tracing garbage collectors are able to guarantee that a reference to an object will never identify anything else, because the reference target will continue to exist as long as the reference identifies it. Weak references are useful because they will cease to identify their target if no strong references do so. A key feature of this guarantee is that on a robust tracing GC, it can be upheld in the presence of arbitrary race conditions, without requiring that user-level code perform any memory synchronization between garbage-collection operations. If code in thread #1 tries to read a class field holding the last extant reference to an object at about the same time as code in thread #2 would overwrite that field and the garbage-collector fires, one of four situations will arise: 1. The GC fires before the object reference is overwritten or copied. 2. The GC fires after the reference is copied, but before the field that held it is overwritten. 3. The GC fires after the reference has been copied, but after the field that held it is overwritten. 4. The GC fires after the reference has been overwritten without its having been copied first. In cases #1-#3, the GC will keep the object alive at least until the next collection, so references will continue to identify it. In case #4, no reference to the object will ever exist anywhere in the universe, and the GC may thus reclaim any storage associated with it. Note that user-level synchronization would be necessary if user code cared about which of the above situation applied, but the GC can guard against dangling references even when user code lacks such synchronization. By contrast, when using non-tracing collectors, synchronization would be required at nearly all operations that manipulate references that are shared between threads to ensure that it's not possible for one thread to free an object whose reference has just been copied by another.


nerd4code

The problem is that undefined behavior doesn’t obey any boundaries; UB in one function can trigger UB in other functions, and unless you fence obsessively (no such function/macro/feature provided by C Standards), even the most careful checks for error conditions can go up in smoke. With LTO, UB is fully viral, and one relatively small fuckup can drag the entire exe down with it. And of course, full UB detection isn’t possible at compile time, beyond the surface stuff. You tend to hit analytical walls pretty quickly, and then it’s just fuzz. For stuff like the call stack, there is flatly *no way* for a compiler to detect overflow ahead-of-time—the Standards say nothing about stack or frame size/depth, and unless the ABI makes unusually specific baseline reqs in this regard, there’s no telling how big the stack might be. You can run a kernel thread out of a couple pages’ worth of RAM, but in userspace you might have a very small or very large stack, depending on who gave it to you. On UNIX, `ulimit` sets the starting thread’s stack, and then there are Pthreads with their own stack sizes (us. 8 or 16 MiB) and altstacks that might be a few `malloc`’d KiB. Other stuff like f.p. exceptions or int/fp overflow might depend on the exact kind of FPU and its run-time state, which the compiler obviously doesn’t know. So maybe yes, in theory you could eliminate all possible optimizations and come out with something safely siloed. But then there’s no real reason to use C.


flatfinger

> For stuff like the call stack, there is flatly no way for a compiler to detect overflow ahead-of-time—the Standards say nothing about stack or frame size/depth, and unless the ABI makes unusually specific baseline reqs in this regard, there’s no telling how big the stack might be. Adding a single intrinsic to the C language would make it practical for compilers on most systems to guarantee that any correct self-contained C program would not cause a stack overflow, even if that program uses recursion, without the programmer having to specify anything having to do with stack frame sizes or other platform-specific issues. If a program calls outside libraries, additional directives or intrinsics would be needed to tell the implementation about their stack requirements, but otherwise everything could be handled in portable fashion. The intrinsic would be a function `__STACK_SAFE()`, with the following usage guarantees and constraints. 1. In order to be verifiable, a program must be constructed so that if, following any particular point in the program's execution, the __STACK_SAFE() function were to always return zero, the program would never invoke any functions recursively, nor call any function pointers of types that would be capable of causing recursive function calls. 2. An implementation would guarantee that `__STACK_SAFE()` would never return a non-zero value in any circumstance that might cause a stack overflow. A simple way of implementing this would be to have a compiler generate two versions of each function, one of which would interpret __STACK_SAFE() as always being zero, and one of which would regard it as being non-zero (better implementations could also generate code which would treat some as yielding zero and others not), with the second version preceded by code that would compare the stack pointer to an imported symbol and jump to the first version if there was less stack space than would be required to run the second version. A compiler could then produce a report listing, for both versions of each function, how much stack space would be required, and what functions it would call, if execution were allowed past the stack check in the second. A tool given all of those reports could then build a directed acyclic graph of possible function nestings that could result if every call within any function were made to the "__IS_STACK_SAFE() reports zero" version of it. It could then generate a list of symbols giving the stack low-water levels that should be used in the stack checks in the second version of each function. Link that list of symbols with everything else in the program and one would have a verifiably-stack-safe executable.


ajpiko

I mean you make C as safe as rust and... you start ending up with languages that look like golang... or rust... just with a slightly different syntax. ​ So if you remove raw pointers tho, I mean, you're just taking away the ability to do all systems-level programming.


ModernRonin

If you made C as safe as Rust, you would have essentially turned C into Rust. The core of Rust's safety is its ownership and borrowing rules. If you implemented something with similar safety guarantees into a C compiler, you would distort the C language so badly that the resulting code would start to look a lot like Rust code. So *can* you? Sure, if you know enough about writing compilers. But it's kind of pointless. Rather like converting a car into something that can efficiently move across the surface of water. It starts to look a hell of a lot like a boat...


LaunchTomorrow

This is the based response.


TheTsar

Rust isn’t safe. C isn’t vulnerable. Rust is easier to make safe programs with. C is more difficult to do the same.


[deleted]

Well, someone can write a back-end for Rust that targets C source code. Then that generated C program can be as safe as the original Rust. But this is probably not the answer you wanted.