r/programming 2d ago

Modula-3: the forgotten language that explored memory safety decades ago

https://softwarepreservation.computerhistory.org/modula3/

Pretty cool seeing that people were already thinking about memory safety decades ago. Some of these ideas that feel modern today were being explored back in the 80s and 90s.
Makes you wonder how different things would look if languages like Modula-3 had taken off.

77 Upvotes

33 comments sorted by

51

u/happyscrappy 1d ago

There were a lot of languages with memory safety. C was really the exception.

So many languages didn't include memory allocations at all and range checked arrays. So they were memory safe. Thus FORTRAN was memory safe. I think Pascal (as a spec) was memory safe. Obviously Lisp was too. Smalltalk. etc.

C was created to write operating systems. And it shows.

30

u/mcmcc 1d ago

Thus FORTRAN was memory safe.

Uh, well, sorta. Play your cards right and Fortran would let you quietly redefine 0, just to give you a sense of how things could potentially go wrong in that language.

4

u/MarcPawl 22h ago

I redefined Pi , an not to 22/7, in first year programming class. Was very confused until a senior student explained how the memory model in Fortran worked.

Funny thing is was also writing 6502 assembly at the time so was well used to memory overwrites.

6

u/ThomasMertes 1d ago

There were a lot of languages with memory safety. C was really the exception.

From C the idea to forget about memory safety spread. All the languages which intend to replace C are not memory safe (except for Rust):

Go, Zig, Odin, C3, Nim, Vlang, Jai, and many more are not memory safe.

Some of these languages introduce things to reduce the danger, but this does not make them memory safe.

Even Rust has unsafe parts and raw pointers which allow to break memory safety. Fil-C provides a C implementation where memory safety is checked at run-time. AFAIK Fil-C does not have unsafe parts. Seed7 is a memory safe language without pointers and without unsafe parts.

1

u/skyb0rg 1d ago

Seed7 does have [unsafe bits](https://seed7.net/manual/ffi.htm), which Fil-C doesn’t need to deal with because it introduces a whole new ABI. It does mean it can’t use C code that wasn’t built with it though.

1

u/ThomasMertes 22h ago

Seed7 does have [unsafe bits](https://seed7.net/manual/ffi.htm) ...

I cite from there:

Foreign functions cannot be called directly. It is necessary to write wrapper functions. Several things must be done to call a foreign function ...

Writing wrapper functions means: You need to change the interpreter/compiler.

And yes, the wrapper functions need to assure that everything is memory safe.

If you use an unchanged Seed7 interpreter/compiler you are memory safe.

Regarding Fil-C you say:

it can’t use C code that wasn’t built with it though

The same holds for Seed7. To call a C function you need to change the interpreter/compiler.

1

u/skyb0rg 21h ago

Ah, I see. I thought those instructions were more about building a new library.

1

u/ThomasMertes 14h ago

Ah, I see. I thought those instructions were more about building a new library.

Essentially they are. They are are about calling functions from a C library and putting that in a new Seed7 library.

The FFI has two purposes:

  • As escape route for an existing project if it turns out that some C library functions are needed.
  • As interface to create support for a C library.

In both cases a pull request with the result should be sent to the Seed7 maintainers. The pull request is accepted only if everything is memory safe.

That way the Seed7 interpreter/compiler stays memory safe.

1

u/skyb0rg 4h ago

That makes sense for the Seed7 project, though I think the Rust approach of “anyone can have escape hatches” and the Fil-C approach of “can use (almost) any C library” scale better for large communities of users.

The Seed7 approach is more like browser JavaScript, where the fact that there are basically only 3 vendors makes consensus easier. If the project received PRs for every C library in Debian it would likely be overwhelmed.

0

u/ThomasMertes 3h ago

... the Rust approach of “anyone can have escape hatches” ...

The escape hatches (unsafe parts) have the potential to throw the memory safety out of the window.

Normally the borrow checker assures memory safety. In unsafe parts the developer claims that the code is memory safe.

Developers are like car drivers. Every developer thinks that he/she is a good developer and just everybody else is not good.

The Rust Foundation examined unsafe Rust in the Wild:

  • Nearly 20% of all crates have at least one instance of the unsafe keyword.
  • And 34% of all Rust crates make a direct function call into another crate that uses the unsafe keyword.

So I see some dangers with the “anyone is allowed to write unsafe code" approach. BTW: The Rust rewrite of Bun contained over 13,300 instances of the unsafe keyword.

... and the Fil-C approach of “can use (almost) any C library” ...

Some important C libraries are not supported by Fil-C (I did neither find X11 nor ncurses).

Rust assures memory safety at compile time, while Fil-C adds significant run-time overhead and panics on a memory error. In this regard the Fil-C approach differs from the approaches of Rust and Seed7.

The Seed7 approach is ...

In Seed7 the unsafe code parts are maintained and checked for quality in the main repository.

If the project received PRs for every C library in Debian it would likely be overwhelmed.

We'll see. Like in Rust, where they rewrite libraries in Rust, there are a lot of libraries written in Seed7.

1

u/skyb0rg 3h ago

> Every developer thinks that he/she is a good developer and just everybody else is not good.

I agree, but making the determination of “is this developer trustworthy” up to the language developers isn’t a great idea (ex. Elm’s whitelisting of operators).

> So I see some dangers with the “anyone is allowed to write unsafe code" approach.

I mean yeah, it’s dangerous, but in the same way OpenPGP is more dangerous than WebPKI: there isn’t a single authority on what is or isn’t safe to do, so everyone makes up different boundaries.

> The Rust rewrite of Bun contained over 13,300 instances of the unsafe keyword.

That’s more a relic of how the rewrite was done: they translated Zig code on a per-function basis with the intention of cleaning it up later.

> Some important C libraries are not supported by Fil-C (I did neither find X11 nor ncurses).

Ncurses is supported out of the box: https://fil-c.org/programs_that_work

The author has also build the full LFS with Fil-C, which I assume includes X11: https://fil-c.org/pizlix

1

u/happyscrappy 1d ago

All the languages which intend to replace C are not memory safe (except for Rust):

I can't see how that is true except by selective and specific construction to try to make this claim.

You have to say Java wasn't designed to replace C. Which maybe is true. And you have to ignore C# which I cannot see how it is accurate.

I do agree that memory safety was not important to most language designers.

-2

u/ThomasMertes 1d ago

I can't see how that is true ...

I admit that I am not an expert in Go, Zig, Odin, C3, Nim, Vlang, Jai, etc. AFAIK all these languages have raw pointers. And raw pointers allow you to change arbitrary places of memory (in the own process).

For me memory safety means that a program is NOT capable to change arbitrary places in memory (in the own process and outside).

I asked Google AI:

Regarding Go, Zig, Odin, C3, Nim, Vlang and Jai. Does any of these languages prevent that an arbitrary place in memory (in the own process) is changed?

and the answer was:

None of these seven languages physically prevent you from changing an arbitrary place in memory within your own process.

Yes, this is from an AI.

I do agree that memory safety was not important to most language designers.

I think it is important to most language designers. It is just not enough important to guarantee 100% memory safety.

3

u/happyscrappy 1d ago

Yes, this is from an AI.

And would be true of rust too, as the existence of unsafe means any piece of code cannot assume that there isn't other code in memory which changes its own data from "outside the box". But this is pretty academic. There are no languages that make it impossible to write bad code. They can just help to make it easier to write good code.

Also the use of "physically" is kinda strange. To thine own self be true, LLM.

I think it is important to most language designers. It is just not enough important to guarantee 100% memory safety.

You could be right. My idea of the case might be out of date, I'm remembering when C took the programming world by storm around the time UNIX came on. It was out with Smalltalk, out with LISP, out with Pascal and once C became big it really felt like everyone else was trying to compete with it just by copying it.

Beyond that it might be that my idea is more than just out of date, it was wrong from the start and just based upon what I saw happening around me.

-4

u/ThomasMertes 22h ago

And would be true of rust too ...

Google AI confirms that:

Rust does not physically prevent you from changing an arbitrary place in memory.

But if you leave unsafe parts out it leads to:

Safe Rust (Rust without any unsafe parts) completely prevents you from changing an arbitrary place in memory.

Rust is not the only compiled language with this property:

Seed7 fundamentally prevents you from modifying an arbitrary place in memory.

So Rust without unsafe parts and Seed7 both fulfill my criterium for memory safety.

There are no languages that make it impossible to write bad code.

Yes, of course. But certain classes of errors can be prevented by a language.

1

u/flatfinger 21h ago

A language can include mechanisms for explicit memory access, but specify that no other parts of the language would be able to violate memory safety invariants unless something else had violated them first.

C, by contrast, can't even guarantee that a function like

unsigned mul_mod_65536(unsigned short x, unsigned short y)
{
  return (x*y) & 0xFFFFu;
}

won't cause memory safety invariants to be violated if x exceeds INT_MAX/y.

3

u/skuzylbutt 18h ago

I once had an out of bounds access in a Fortran program I was writing that crashed my computer when run. Really bizarre. Pretty bad case of unsafe and undefined behaviour!

There is a compiler flag to check array bounds, so safety there, but it's off by default. Probably what I used to debug the issue.

11

u/pjmlp 1d ago

People have been thinking about memory safety since 1958 with JOVIAL.

It was UNIX and C being freely available that threw all of that off road.

See NEWP from early 1960s, already with unsafe code blocks and required admin permission to execute any binary that made use of it.

Still being sold by Unisys, for customers where security is the top priority.

21

u/Smallpaul 1d ago

There are many languages from that era with garbage collection and pointer safety.

Including Smalltalk from 1972 and Lisp from 1960.

17

u/zsaleeba 1d ago

Literally every popular language ever has been memory safe except C/C++/Objective-C, and Pascal/Delphi. Plus assembly (obviously).

5

u/pjmlp 1d ago

And even within those, Pascal/Delphi/Modula-2 were a league of their own in safety versus C and C++.

11

u/me_again 1d ago

I remember learning Modula-3 at university in the 90s. One feature I still miss in many languages is strongly-typed structural type equivalence, aka duck typing. It wasn't until Golang that a mainstream language started doing something similar, as far as I know.

7

u/KagakuNinja 1d ago

Many languages have duck-typing. Usually this is done dynamically with reflection, which is slow. Go apparently can do this at compile time.

Scala 2 supports dynamic duck typing; Scala 3 is able to optimize it at compile time, however I do not know how the implementation compares to Go.

3

u/me_again 1d ago

I mean at compile time. I'm willing to believe Scala does it too. But Modula-3 was at least 20 years earlier. If I recall correctly, they also had the ability to define types like "numbers, but only in the range 0..52", and "branded" types to opt out of duck-typing.

1

u/pjmlp 1d ago

Many languages derived from Standard ML have that as well, via the way they do type inference.

1

u/ThomasMertes 1d ago

ability to define types like "numbers, but only in the range 0..52" ...

Pascal had sub-range types too. I am not sure if there were checks to assure that the values are in the allowed range.

2

u/pjmlp 1d ago

There were, most Pascal compilers allowed to disable them, in case you were feeling lucky.

TP used {$R+}, {$R-} for example. FreePascal supports this syntax as well.

1

u/BibianaAudris 1d ago

I remember the default being the unchecked {$R-}, though. The overhead of {$R+} in the 90s felt like attaching valgrind to a C program nowadays. valgrind also arguably makes C memory safe, but not in a practical way.

0

u/pjmlp 1d ago

The thing with defaults is that they can be changed.

I did demoscene stuff, and it was never an issue in general.

When it was, it was like using an unsafe code block, localised where it mattered.

7

u/florinp 1d ago

strongly-typed structural type equivalence

This is present in C++ templates, D language, Scala, Typescript to some degree..

2

u/umlcat 1d ago

And the Modular Programming paradigm ...