25

I want a better C. Let me explain:

I do a lot of programming in C, which is required for applications that have real-time needs such as audio programming, robotics, device drivers, etc.

While I love C, one thing that gets on my nerves after having spent a lot of time with Haskell is the lack of a proper type system. That is, as soon as you want to write a more general-purpose function, say something that manipulates a generic pointer, (like say a generic linked list) you have to cast things to void* or whatever, and you loose all type information. It's an all-or-nothing system, which doesn't let you write generic functions without losing all the advantages of type checking.

C++ doesn't solve this. And I don't want to use C++ anyways. I find OO classes and templates to be a headache.

Haskell and its type classes do solve this. You can have semantically useful types, and use type constraints to write functions that operate on classes of types, that don't depend on void.

But the domain I'm working in, I can't use Haskell, because it's not real-time capable--mostly due to garbage collection. GC is needed because it's very difficult to do functional programming, which is allocation-heavy, without automatic memory management. However, there is nothing specifically in the idea of type classes that goes against C's semantics. I want C, but with Haskell's dependable type system, to help me write well-typed systems. However, I really want C: I want to be in control of memory management, I want to know how the data structures are layed out, I want to use (well-typed) pointer arithmetic, I want mutability.

Is there any language like this? If so, why is it not more popular for low-level programming?

Aside: I know there are some small language experiments in this direction, but I'm interested in things that would be really usable in real-world projects. I'm interesting in growing-to-well-developed languages, but not so much "toy" languages.

I should add, I heard of Cyclone, which is interesting, but I couldn't get it to compile for me (Ubuntu) and I haven't heard of any projects actually using it.. any other suggestions in this vein are welcome.

Thanks!

Steve
  • 1,452
  • 1
  • 13
  • 17
  • 10
    Uh, have you EVER used C++? It features many, many generic useful functions like sort that maintain all type data, and aren't object-orientated. Templates have nothing to do with object orientation, although they are a good combination. C++ does not enforce object orientation in any way, and it's perfectly valid to make templated free functions that are generic and maintain all type data. – Puppy Aug 24 '10 at 13:47
  • 6
    As I said in the question, not interested in C++, thanks. – Steve Aug 24 '10 at 13:48
  • Did you actually check the Haskell's GC behaviour in your case? It's smarter than your average OO language and you can fine tune it at the application start to your actual need. It's quite likely that you can have almost no stop-the-world effects visible at all. (assuming you're writing soft real-time) – viraptor Aug 24 '10 at 14:00
  • Why wouldn't you continue with Haskell? [It's speed is not bad at all](http://shootout.alioth.debian.org/u32/which-programming-languages-are-fastest.php) ;) – Esteban Küber Aug 24 '10 at 14:28
  • A really strict type system like Haskell's doesn't work as well in the sort of low-level projects you're doing. BTW, why are you not interested in C++? The reason you give is that OO and templates give you headaches: if they do, what gives you the belief that you can write device drivers and the like without crippling headaches? – David Thornley Aug 24 '10 at 14:29
  • 3
    +1 because I would love an imperative language with a type system half as awesome as Haskell's –  Aug 24 '10 at 14:30
  • 3
    Just to nip this in the bud: I already know about C++. C++ templates, while useful, are _not_ the same thing as Haskell's type classes. Every single answer so far has suggested a GC'ed language, showing that you are not understanding my question. David: _Why_ is Haskell's strict type system not appropriate for low-level programming? Voyager: Real-time programming is not about speed, it's about time determinism. GC'ed languages do not provide this unless they have a real-time GC, but I know of no languages which have that. In any case, I specified that I want manual memory management. – Steve Aug 24 '10 at 14:58
  • @Steve: You'll have no determinism from the task switcher itself anyway, unless you're running a hard-rt system to begin with. If you're ok with soft-realtime, then tune your GC to respond quickly in 99.999% of cases and you're done. Haskell's memory usage / GC duty is fairly easy to monitor / tune compared to other languages. – viraptor Aug 24 '10 at 15:15
  • 4
    "I find OO classes and templates to be a headache." Templates and OOP are powerful instruments in C++, but if you don't like them, then you don't have to use them. – SigTerm Aug 24 '10 at 16:18
  • @viraptor: "task switcher" -- I didn't say this was necessarily for desktop operating systems. There are many situations in which C does indeed provide hard real-time performance. – Steve Aug 24 '10 at 16:59
  • @SigTerm: "you don't have to use them"--thus losing any advantage of C++ over C? – Steve Aug 24 '10 at 17:02
  • 1
    @Steve: *"thus losing any advantage of C++ over C?"* You forgot about exception handling, safer allocations using new/new[]/delete/delete[] instead of malloc, boolean types, namespaces, and a bit shorter syntax. You lose a lot without classes(RAII, operator overloading) and templates(std::string, std::min, std::sort, std::list, std::vector), but you don't lose everything. – SigTerm Aug 24 '10 at 17:16
  • @Steve: When you wrote "device drivers" I thought you meant the PC device drivers, not the other side. In that case - yes - almost any GC will suck... (*almost* - still worth checking) – viraptor Aug 24 '10 at 20:06
  • You couldn't figure out that C/C++ are crap without learning Haskell first? :/ Templates and OO are only a headache in C++. – L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ Dec 07 '10 at 06:53
  • 2
    It's clear that C++ is not an option for the poster. However, I think the problem here is the statement "C++ doesn't solve this", because it's not a true statement. It does solve it. Having said that, if I had to stay with C for some reason, and I had the problem described with regard to avoid void* casts, I would employ a similar method that we used to use in C++ prior to the introduction of templates, which is to isolate those casts to utility functions to minimize their proliferation across the application layer of the code. – zumalifeguard Mar 06 '14 at 17:13
  • *Warning: C++ total beginner here!* I've been toying with pure virtual classes lately, and using those as interfaces for generic functions/etc.. I think they kinda compare to Haskell Type-Classes (which I think are *beautiful*, though Haskell is below C++ on my experience ladder). – Narfanar May 15 '15 at 08:08

9 Answers9

24

Since nobody brought it up yet: I think the ATS language is a very good candidate for a better C! Especially since you enjoy Haskell and thus functional programming with strong types. Note that ATS seems to be specifically designed for systems programming and hard real-time applications as most of it can do without garbage collection.

If you check the shootout you will find that performance is basically on par with C. I think this is quite impressive since modern c compilers have years and years and years of optimization work behind them while ATS is basically developed by one guy. -- while other languages providing similar safety features usually introduce overhead ATS ensures things entirely at compile time and thus yields very similar performance characteristics as C.

To quote the website:

What is ATS?

ATS is a statically typed programming language that unifies implementation with formal specification. It is equipped with a highly expressive type system rooted in the framework Applied Type System, which gives the language its name. In particular, both dependent types and linear types are available in ATS. The current implementation of ATS (ATS/Anairiats) is written in ATS itself. It can be as efficient as C/C++ (see The Computer Language Benchmarks Game for concrete evidence) and supports a variety of programming paradigms that include:

Functional programming. The core of ATS is a functional language based on eager (aka. call-by-value) evaluation, which can also accommodate lazy (aka. call-by-need) evaluation. The availability of linear types in ATS often makes functional programs written in it run not only with surprisingly high efficiency (when compared to C) but also with surprisingly small (memory) footprint (when compared to C as well).

Imperative programming. The novel and unique approach to imperative programming in ATS is firmly rooted in the paradigm of programming with theorem-proving. The type system of ATS allows many features considered dangerous in other languages (e.g., explicit pointer arithmetic and explicit memory allocation/deallocation) to be safely supported in ATS, making ATS a viable programming language for low-level systems programming.

Concurrent programming. ATS, equipped with a multicore-safe implementation of garbage collection, can support multithreaded programming through the use of pthreads. The availability of linear types for tracking and safely manipulating resources provides an effective means to constructing reliable programs that can take advantage of multicore architectures.

Modular programming. The module system of ATS is largely infuenced by that of Modula-3, which is both simple and general as well as effective in supporting large scale programming.

In addition, ATS contains a subsystem ATS/LF that supports a form of (interactive) theorem-proving, where proofs are constructed as total functions. With this component, ATS advocates a programmer-centric approach to program verification that combines programming with theorem-proving in a syntactically intertwined manner. Furthermore, this component can serve as a logical framework for encoding deduction systems and their (meta-)properties.

Community
  • 1
  • 1
Paul
  • 7,110
  • 1
  • 36
  • 45
  • 2
    The performance of ATS is highly connected to that of gcc, which it uses as a backend. The main benefit is indeed the much more potent type system supporting dependent types. – didierc Jun 18 '14 at 02:01
  • 1
    @didierc You're right of course. What I wanted to say was that even though many languages compile to C they usually introduce some overhead that a program written in C would not have. ATS's selling point then is that it has all these safety features but that it ensures them entirely at compile time and does not introduce overhead and is thus on-par with C's performance. – Paul Jun 18 '14 at 05:25
  • 1
    I totally agree with you, you make a very important point. Tbh, that's pretty much what got me interested in this language. – didierc Jun 18 '14 at 07:41
22

What about Nimrod or Vala languages ?

Grzegorz Adam Hankiewicz
  • 6,238
  • 1
  • 30
  • 65
user521238
  • 229
  • 2
  • 2
  • 6
    Nimrod is a very cool language. Its syntax is reminiscent of Scala + Python, yet it cross-compiles down to C. So it could be faster for certain use cases. – NT_ Aug 28 '13 at 20:08
  • @NT_ It actually looks significantly closer to Pascal (and, by virtue, ADA) than anything. –  Dec 02 '14 at 13:31
17

Rust

Another (real) candidate for a better C is The Rust Programming Language.

Unlike some other suggestions, (Go, Nimrod, D, ...) Rust can directly compete with C and C++ because it has manual memory management and does not require garbage collection (see [1]).

What sets Rust apart is that it has safe manual memory management. (The link is to pc walton's blog, one of Rusts main contributors and generally worth a read ;) Among other things, this means it fixes the billion dollar mistake of nullpointers. Many of the other languages suggested here either require garbage collection (Go) or have garbage collection turned on by default and do not provide facilities for safe manual memory management beyond what C++ provides (Nimrod, D).

While Rust has an imperative heart, it does borrow a lot of nice things from functional languages, for example sum types aka tagged unions. It is also really concerned with being a safe and performance oriented language.

[1] Right now there are two main pointer types owned pointers (like std::unique_ptr in C++ but with better support from the typechecker) and managed pointers. As the name suggests the latter do require task-local garbage collection, but there are thoughts to remove them from the language and only provide them as a library.

EDITED to reflect @ReneSacs comment: Garbage collection is not required in D and Nimrod.

Paul
  • 7,110
  • 1
  • 36
  • 45
  • 1
    What makes GC-based virtual machines like Java and managed .NET "safe" is not that abandoned things get cleaned up, but rather that because managed code maintains as an absolute 100% invariant that there will never ever be a reachable reference to an object that no longer exists, there is no way a reference to an object can ever become a reachable reference to a different object. I can imagine one could design a "safe manual memory management system" using handles, if every slot in the handle table kept a count of how many times it had been reused, and every handle reference in code kept... – supercat Dec 14 '13 at 21:16
  • ...a copy of the count associated with its handle (so that if a handle was released and a new handle used its slot, any reference to the old handle would be flagged as invalid), but I'm not sure in what cases such a system could outperform a decent garbage collector. – supercat Dec 14 '13 at 21:17
  • 1
    @supercat: What you describe sounds a bit like reference counted pointers, except that those would ensure that the referenced object exists as long as at least one pointer exists (instead of marking them as invalid when the object ceases to exist). However, Rust is unique in that it ensures __at compile time__ that owned pointers are not used after the have relinquished ownership and that references ('borrowed pointers' in Rust) do not outlive the objects they reference. I think this is unique in mainstreamy languages. This achieves memory safety in the sense you describe. – Paul Dec 30 '13 at 11:45
  • Reference-counting is used to keep live references alive; the system I'm describing would be used to ensure that dead references stay dead. As for compile-time validation, I can see no way to have compile-time validation of reference validity in a system which does not place severe restrictions on the kinds of object graphs that can exist, does not use garbage-collection, and does not require thread-synchronization of modifications to references. – supercat Dec 30 '13 at 16:06
  • @supercat Well, you might want to checkout Rust then ;) It does restrict what kind of object graphs can exist, especially in multi-threaded contexts. However, this ensures there is a sensible ownership model in place, so maybe this is a good default? I haven't used it enough to really speak to the practicality of this approach but I think it does hold a lot of promise. – Paul Dec 30 '13 at 16:15
  • In general, mutable objects should have exactly one clearly-defined "owner" which regards their state as being part of its own, though some kinds of entities (e.g. threads or forms) are in a sense "free-standing"; truly shared ownership of mutable objects is rare--mainly appropriate for things like locks. Immutable objects frequently have no concept of ownership, however. Active management is generally good for mutable objects, and not so good for immutable ones. One thing that I would think might be helpful from a GC perspective especially on resource-constrained systems... – supercat Dec 30 '13 at 16:28
  • ...would be a framework-recognized concept of deep immutability. Efficient generational garbage collection requires that the GC knows what older objects haven't been modified (an object which hasn't been modified since the last gen0 collection can't hold any references to gen0 objects, and thus need not be examined during a gen0 collection); for that reason, things like the .NET Embedded Framework don't support generational GC. If a GC could recognize deeply-immutable objects, however, it could promote those to gen1 and gen2 even if it couldn't promote mutable objects. – supercat Dec 30 '13 at 16:32
  • 2
    Nimrod and D don't requires garbage collection (you can compile nimrod with `--gc:None`), only Go does. When disabling it you are not much better than C/C++ on memory management front, but not worse either. Anyway, when you have a complex structure, and Rust's ownership can't cope with it, you are back using a GC in Rust. – ReneSac Mar 30 '14 at 19:53
  • @ReneSac Thank you for pointing this out. If I understand correctly then I still think that Rusts facilities for manual memory management are much nicer, in particular turning GC either completely on or completely off via a command line flag seems pretty crude in comparison. I deposit that Rusts facilities will work for the vast majority of cases and you can use GC / ref counting for the few structues for which it doesn't suffice (provided there are any) instead of for the whole program. Defaults make a world of difference in my experience. – Paul Mar 31 '14 at 07:42
  • @Paul Yes, when not using any GC Rust's manual memory management seems nicer (though I never tried it, so I don't know how difficult it is to do everything in terms of unique pointers and borrows) and probably much more of Rust's the standard library will be usable w/o a GC. Both Nimrod and D allows you to compile with GC but stop it for a part of your program execution. Nimrod also let's you specify a maximum pause time in ms range for when you do run it. Details on [Nimrod GC](http://build.nimrod-lang.org/docs/gc.html) and [D GC](http://dlang.org/phobos/core_memory.html) – ReneSac Apr 01 '14 at 15:28
  • @ReneSac Thank you for the additional info. It's always nice to learn more about these things. As for GC it's all about different tradeoffs I guess. While I think Rust's facilities are pretty nice (especially from a theoretical point of view) they're also not as easy as to use as garbage collection. And there are probably not that many programs that can't afford Nimrod's soft real time guarantees ... – Paul Apr 02 '14 at 07:29
14

I don't know much about Haskell, but if you want a strong type system, take a look at Ada. It is heavily used in embedded systems for aerospace applications. The SIGADA moto is "In strong typing we trust." It won't be of much use, however, if you have to do Windows/Linux type device drivers.

A few reasons it is not so popular:

  • verbose syntax -- designed to be read, not written
  • compilers were historically expensive
  • the relationship to DOD and design committees, which programmers seem to knock

I think the truth is that most programmers don't like strong type systems.

Dr. Watson
  • 3,584
  • 4
  • 28
  • 42
  • 5
    +1. Compared to C, Ada has much better facilities to exactly specify how things are laid out in memory (by representation clauses). Support for real-time programming is included in the language itself, so that there is no need to resort to external libraries. The GNAT compiler is of high quality, and available for free under GPL. Compared to most (all?) C compilers you will be suprised by the detailed level of error reporting the compiler is capable of providing, partially due to the added verbosity. – Schedler Jan 24 '11 at 14:43
  • 1
    I agree with GNAT, I like it very much, and the GNAT errors are so much more helpful than many of the errors that G++ spews. I use GNAT/Ada for all of my personal and graduate school courses. At work however,... – Dr. Watson Jan 25 '11 at 21:56
  • Haskell evolved as a research language, however it can be said to suffer from, or have the advantage of, the power of many features non-orthogonalites. Furthermore, being functional, one has to usually first consider pattern matching rather than sequential temporal causality as in imperative languages. –  Dec 02 '14 at 13:35
13

Nim (former Nimrod) has a powerful type system, with concepts and easy generics. It also features extensive compile time mechanisms with templates and macros. It also has easy C FFI and all the low level features that you expect from a system programming language, so you can write your own kernel, for example.

Currently it compiles to C, so you can use it everywhere GCC runs, for example. If you only want to use Nim as better C, you can do it via the --os:standalone compiler switch, that gives you a bare bones standard library, with no OS ties.

For example, to compile to an AVR micro-controller you can use:

nim c --cpu:avr --os:standalone --deadCodeElim:on --genScript x.nim

Nim has a soft real-time GC where you can specify when it runs and the max pause time in microseconds. If you really can't afford the GC, you can disable it completely (--gc:none compiler switch) and use only manual memory management like C, losing most of the standard library, but still retaining the much saner and powerful type system.

Also, tagged pointers are a planned feature, that ensure you don't mix kernel level pointers with user level pointers, for example.

ReneSac
  • 521
  • 3
  • 9
  • I am still unclear about nim's "type classes". Could you provide pointers about these? Are you thniking about concepts or the equivalent of them in nim? From what I gather, concepts are more groups of constraints on types than what type classes are in Haskell, for instance... – Mateo Jul 29 '15 at 02:50
  • Yes, it would be concepts. They were formerly named "User Defined Type Classes" when I wrote that answer, but then renamed to avoid confusion (and have a nicer shorter name). I will edit the answer swapping the name. – ReneSac Jul 30 '15 at 16:05
10

D might offer what you want. It has a very rich type system, but you can still control memory layout if you need to. It has unrestricted pointers like C. It’s garbage collected, but you aren’t forced to use the garbage collector and you can write your own memory management code if you really want.

However, I’m not sure to what extent you can mix the type richness with the low-level approach you want to use.

Let us know if you find something that suits your needs.

Daniel Cassidy
  • 22,189
  • 4
  • 37
  • 52
7

I'm not sure what state Cyclone is in, but that provided more safety for standard C. D can be also considered a "better C" to some extent, but its status is not very clear with its split-brain in standard library.

My language of choice as a "better C" is OOC. It's still young, but it's quite interesting. It gives you the OO without C++'s killer complexity. It gives you easy access to C interfaces (you can "cover" C structs and use them normally when calling external libraries / control the memory layout this way). It uses GC by default, but you can turn it off if you really don't want it (but that means you cannot use the standard library collections anymore without leaking).

The other comment mentioned Ada which I forgot about, but that reminded me: there's Oberon, which is supposed to be a safe(-er) language, but that also contains garbage collection mechanisms.

viraptor
  • 30,857
  • 7
  • 96
  • 176
5

You might also want to look at BitC. It’s a serious language and not a toy, but it isn’t ready yet and probably won’t be ready in time to be of any use to you.

Nonetheless, a specific design goal of BitC is to support low-level development in conjunction with a Haskell-style type system. It was originally designed to support development of the Coyotos microkernel. I think that Coyotos was killed off, but BitC is still apparently being developed.

Daniel Cassidy
  • 22,189
  • 4
  • 37
  • 52
  • My understanding is that BitC was abandoned when the author was hired by Microsoft? Perhaps I'm wrong. It's definitely along the right lines, although I haven't read up on its type system. – Steve Aug 24 '10 at 17:00
  • 3
    Jonathan S. Shapiro has since left Microsoft and announced his intention to resume work on BitC. See: http://www.coyotos.org/pipermail/bitc-dev/2010-March/001809.html – Daniel Cassidy Aug 26 '10 at 15:03
  • 1
    no news since 2010... is this dead? – Janus Troelsen Feb 07 '14 at 16:56
  • There is still quite a bit of activity on the mailing list: http://www.coyotos.org/pipermail/bitc-dev/2014-April/thread.html – Daniel Cassidy Apr 23 '14 at 22:48
0

C++ doesn't solve this. And I don't want to use C++ anyways. I find OO classes and templates to be a headache.

Get over this attitude. Just use C++. You can start with coding C in C++ and keep gradually moving to better style.

wilx
  • 16,767
  • 5
  • 53
  • 109
  • 7
    Get over this attitude. Just use Assembly. You can start with coding Assembly and keep gradually moving to better style. :-P – Skrylar Nov 23 '14 at 23:27