On Mon, 28 Sep 2026 02:59:06 -0700[...]
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index
variables.
In Rust, the only acceptable array index is of type "usize", which
is effectively the same as u64 on most platforms these days, but
does not need to be so: On a 32-bit target, it would be the same
as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
[snip]
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
In article <[email protected]>,
Michael S <[email protected]> wrote:
[snip]
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
Beginning Rust programmers tend to go through a few phases,
particularly if coming from a language like C. "Fighting the
Borrow Checker" is well-known; "traits are awesome; let's use
them everywhere!" is another.
When I started programming in Rust, most of my immediate
colleagues and I had terribly little experience in the language.
We were excited about the its potential benefits, but realized
it would take a few months to build up a sufficient level of
familiarity to use it skillfully. One quipped, "this language
has a near-vertical learning curve."
At one point a coworker (self-deprecatingly) said, "look, I'm
writing crust! C in Rust syntax!" Everyone thought this was a
great play on words, but he was correct: the programs we were
writing were essentially C programs, just expressed in Rust. We
quickly adopted that to describe our still naive code: it wasn't
an insult, just an expression that we were still coming to terms
with a new way of doing things. Some of the hallmarks of that
included using the wrong paradigms (and types!) for lots of our
code.
I'd be curious to know more about your program.
I've written
several hundred kloc of Rust at this point, and while what I
write may be terribly specialized, I rarely find myself indexing
an array; often, I'm using iterators or some other abstraction
for that kind of thing. If that was the one program you have
written in Rust, my _guess_ is that you were using paradigms
appropriate to a different language, which is why you felt you
needed to reach for casts to index an array so frequently.
Again, that's not a comment on your program, so much as
suggesting an impedence mismatch between what you wrote and how
one would generally expect to use the language.
In general, I think a person's experience writing a single
program tells us very little.
- Dan C.
In article <[email protected]>,
Michael S <[email protected]> wrote:
[snip]
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
Beginning Rust programmers tend to go through a few phases,
particularly if coming from a language like C. "Fighting the
Borrow Checker" is well-known; "traits are awesome; let's use
them everywhere!" is another.
When I started programming in Rust, most of my immediate
colleagues and I had terribly little experience in the language.
We were excited about the its potential benefits, but realized
it would take a few months to build up a sufficient level of
familiarity to use it skillfully. One quipped, "this language
has a near-vertical learning curve."
At one point a coworker (self-deprecatingly) said, "look, I'm
writing crust! C in Rust syntax!" Everyone thought this was a
great play on words, but he was correct: the programs we were
writing were essentially C programs, just expressed in Rust. We
quickly adopted that to describe our still naive code: it wasn't
an insult, just an expression that we were still coming to terms
with a new way of doing things. Some of the hallmarks of that
included using the wrong paradigms (and types!) for lots of our
code.
Dan Cross wrote:
In article <[email protected]>,
Michael S <[email protected]> wrote:
[snip]
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
Beginning Rust programmers tend to go through a few phases,
particularly if coming from a language like C. "Fighting the
Borrow Checker" is well-known; "traits are awesome; let's use
them everywhere!" is another.
When I started programming in Rust, most of my immediate
colleagues and I had terribly little experience in the language.
We were excited about the its potential benefits, but realized
it would take a few months to build up a sufficient level of
familiarity to use it skillfully. One quipped, "this language
has a near-vertical learning curve."
At one point a coworker (self-deprecatingly) said, "look, I'm
writing crust! C in Rust syntax!" Everyone thought this was a
great play on words, but he was correct: the programs we were
writing were essentially C programs, just expressed in Rust. We
quickly adopted that to describe our still naive code: it wasn't
an insult, just an expression that we were still coming to terms
with a new way of doing things. Some of the hallmarks of that
included using the wrong paradigms (and types!) for lots of our
code.
I have definitely noted this in my own code, crust is a good name for it.
Quite often, when "fighting the borrow checker", typically because I'm updating a complicated data structure in two locations at the same time, I'll fall back to "write Fortran in Rust", with every access indexed.
This is BTW one of those problems the brand new upcoming borrow checker
is supposed to be far better at figuring out: If I'm updating totally separate parts of the same larger structure, then it should be OK to
mutate both at the same time.
I do realize that there are far more Rusty ways to solve this type of
issue, they just don't come naturally to me yet.
Terje
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables.
In Rust, the only acceptable array index is of type "usize", which is
effectively the same as u64 on most platforms these days, but does not
need to be so: On a 32-bit target, it would be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial underpinnings of Rust safety. Even more importantly, all such indices
are verified against the actual array upper boundary. [...]
It is obvious that you are right; the the existing choices as documented
in the standard and your post above (thanks for that) don't permit what
I want. I apologize for not recognizing that up front. But I maintain
that there should be a category that essentially says "You can do what
you want, but you must document what you did." Furthermore, if such a choice existed, most instances of undefined should be "reclassified"
into that new choice. I think that would aid programmers understanding.
On 26/09/2026 16:25, Stefan Monnier wrote:
David Brown [2026-09-25 19:00:26] wrote:
A halfway point might be for the compiler to emit a warning when it
can't show that `unreachable()` is indeed unreachable.
I can certainly agree that the compiler could give a warning if the unreachable() is definitely reached. And if compiler writers can figure
out some decent heuristics about "unreachable()" being inevitably
reached after some useful code path, then a warning would be nice.
(That can't be given specifications in the standards, because you could
not define clear semantics here - but it's fine to have optional
compiler warnings on things that appear to be wrong according to some
kind of pattern recognition.)
But it would be virtually useless to have a warning any time the
compiler cannot prove that the "unreachable()" is indeed unreachable.
The whole point of its use is when the programmer knows more than the compiler, and is giving the compiler additional information. If the compiler knows that point in the code cannot be reached, then it already knows it is unreachable, and will already optimise on the assumption
that the point is not reached!
[ I generally like the idea of programmers being able to request
specific optimizations and to be warned by the compiler when those
requests can't be satisfied. So we don't have to go dig in the
assembly output to confirm whether the compiler did the right thing or
not. ]
I agree on that principle - I'm a big fan of warnings when my code
appears to be wrong. But this is not a situation where warnings would
be appropriate.
Stephen Fuld <[email protected]d> wrote:
It is obvious that you are right; the the existing choices as documented
in the standard and your post above (thanks for that) don't permit what
I want. I apologize for not recognizing that up front. But I maintain
that there should be a category that essentially says "You can do what
you want, but you must document what you did." Furthermore, if such a
choice existed, most instances of undefined should be "reclassified"
into that new choice. I think that would aid programmers understanding.
In case of gcc 'ureachable()' can be given simple definition:
optimizer will assume that 'ureachable()' will never be
executed and do program transformations based on that knowledge.
Consder the following program fragment:
void foo1(int i) {
if (i < 0) {
unreachable();
}
foo2(i);
}
void foo2(int i) {
if (i < 0) {
foo3(i);
} else {
foo4(i);
}
}
In foo1 compiler could infer that 'i >= 0'. It could also inline
'foo2' into 'foo1'. After inlining it could infer that 'foo4'
will be called. So compiler could transform 'foo1' into
void foo1(int i) {
foo4(i);
}
If ever 'foo1' gets called with negative argument you would get
call to 'foo4' with negative argument.
Some people have mental model that C is compiled on statement by
statement basis. In compiler that is doing simple statement by
statement compilation 'if' in 'foo2' would prevent call to
'foo4' with negative argument. If compiler simply replaced
'unreachable()' by a trap instruction, then program would
trap in ;foo1'. But undefined behaviour opens essentially
unlimied possibilites for program transformation, based on
surrouding code.
BTW: I can image code using a macro
#define ASSERT_FACT(x) {if (x) unreachable();}
and codebase sprinkled with statements like:
ASSERT_FACT(i >= 0 && i < 100)
So 'unreachable()' essentially allows passing arbitrary assertions
to the compiler.
Let me add that given enough facts compiler should be able to
prove that all of them are true. It would be nice to have an
option so that compiler warns if it can not prove that some
'unreachable()' is really unreachable.
And note that false statement implies everthing, so if your
'unreachable()' is wrong then there are unlimited possibilities
for unexpected behaviour.
Terje Mathisen <[email protected]> writes:
[on learning Rust]
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
(Please excuse my pursuing this non-arch topic...)
Question for any Rusters in the group --
What is the best was to learn the Rust programming language?
Which books or other documents are the best introductory or
reference materials?
Disclaimer: My experience with Rust is more than zero but
not a lot more than zero.
Tim Rentsch wrote:
Terje Mathisen <[email protected]> writes:
[on learning Rust]
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
(Please excuse my pursuing this non-arch topic...)
Question for any Rusters in the group --
What is the best was to learn the Rust programming language?
Which books or other documents are the best introductory or
reference materials?
Disclaimer: My experience with Rust is more than zero but
not a lot more than zero.
My personal Rust journey has been driven by a desire to match or
beat the fastest known times for the Advent of Code puzzles, from
2015 to 2025.
Stephen Fuld <[email protected]d> wrote:
It is obvious that you are right; the the existing choices as
documented in the standard and your post above (thanks for that)
don't permit what I want. I apologize for not recognizing that up
front. But I maintain that there should be a category that
essentially says "You can do what you want, but you must document
what you did." Furthermore, if such a choice existed, most
instances of undefined should be "reclassified" into that new
choice. I think that would aid programmers understanding.
In case of gcc 'ureachable()' can be given simple definition:
optimizer will assume that 'ureachable()' will never be
executed and do program transformations based on that knowledge.
Consder the following program fragment:
void foo1(int i) {
if (i < 0) {
unreachable();
}
foo2(i);
}
void foo2(int i) {
if (i < 0) {
foo3(i);
} else {
foo4(i);
}
}
In foo1 compiler could infer that 'i >= 0'. It could also inline
'foo2' into 'foo1'. After inlining it could infer that 'foo4'
will be called. So compiler could transform 'foo1' into
void foo1(int i) {
foo4(i);
}
If ever 'foo1' gets called with negative argument you would get
call to 'foo4' with negative argument.
Some people have mental model that C is compiled on statement by
statement basis. In compiler that is doing simple statement by
statement compilation 'if' in 'foo2' would prevent call to
'foo4' with negative argument. If compiler simply replaced
'unreachable()' by a trap instruction, then program would
trap in ;foo1'. But undefined behaviour opens essentially
unlimied possibilites for program transformation, based on
surrouding code.
BTW: I can image code using a macro
#define ASSERT_FACT(x) {if (x) unreachable();}
and codebase sprinkled with statements like:
ASSERT_FACT(i >= 0 && i < 100)
So 'unreachable()' essentially allows passing arbitrary assertions
to the compiler.
Let me add that given enough facts compiler should be able to
prove that all of them are true. It would be nice to have an
option so that compiler warns if it can not prove that some
'unreachable()' is really unreachable.
And note that false statement implies everthing, so if your
'unreachable()' is wrong then there are unlimited possibilities
for unexpected behaviour.
Thomas Koenig <[email protected]> writes:
Bubblesort is the worst of non-joke sorting algorithms, see
the quote from Numerical Recipes...
For people who don't like bubble sort, or for inclusion in a set
of benchmarks, I suggest the following recently discovered
sorting algorithm (written in C-ish pseudocode):
void
baffle_sort( unsigned n, int *elements ){
for( unsigned i = 0; i < n; i++ ){
for( unsigned j = 0; j < n; j++ ){
if( elements[i] < elements[j] ){
/* exchange elements[i] and elements[j] */
}
}
}
}
(Disclaimer: not original.)
That's good to know but what is a good resource to learn the Rust
language? I've looked at the Rust book several times and each
time come away rather frustrated.
Tim Rentsch <[email protected]> wrote:
Thomas Koenig <[email protected]> writes:
Bubblesort is the worst of non-joke sorting algorithms, see
the quote from Numerical Recipes...
For people who don't like bubble sort, or for inclusion in a set
of benchmarks, I suggest the following recently discovered
sorting algorithm (written in C-ish pseudocode):
void
baffle_sort( unsigned n, int *elements ){
for( unsigned i = 0; i < n; i++ ){
for( unsigned j = 0; j < n; j++ ){
if( elements[i] < elements[j] ){
/* exchange elements[i] and elements[j] */
}
}
}
}
(Disclaimer: not original.)
AFAICS this is very old "sorting by choice" algortim,
just written
in inefficient way (using swaps and keeping running max in the
array instead of using extra variable).
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
Terje Mathisen <[email protected]> writes:
[on learning Rust]
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
(Please excuse my pursuing this non-arch topic...)
Question for any Rusters in the group --
What is the best was to learn the Rust programming language?
Which books or other documents are the best introductory or
reference materials?
Disclaimer: My experience with Rust is more than zero but
not a lot more than zero.
My personal Rust journey has been driven by a desire to match or
beat the fastest known times for the Advent of Code puzzles, from
2015 to 2025.
That's good to know but what is a good resource to learn the Rust
language? I've looked at the Rust book several times and each
time come away rather frustrated.
[email protected] (Waldek Hebisch) writes:
Stephen Fuld <[email protected]d> wrote:
It is obvious that you are right; the the existing choices as
documented in the standard and your post above (thanks for that)
don't permit what I want. I apologize for not recognizing that up
front. But I maintain that there should be a category that
essentially says "You can do what you want, but you must document
what you did." Furthermore, if such a choice existed, most
instances of undefined should be "reclassified" into that new
choice. I think that would aid programmers understanding.
In case of gcc 'ureachable()' can be given simple definition:
optimizer will assume that 'ureachable()' will never be
executed and do program transformations based on that knowledge.
Consder the following program fragment:
void foo1(int i) {
if (i < 0) {
unreachable();
}
foo2(i);
}
void foo2(int i) {
if (i < 0) {
foo3(i);
} else {
foo4(i);
}
}
In foo1 compiler could infer that 'i >= 0'. It could also inline
'foo2' into 'foo1'. After inlining it could infer that 'foo4'
will be called. So compiler could transform 'foo1' into
void foo1(int i) {
foo4(i);
}
If ever 'foo1' gets called with negative argument you would get
call to 'foo4' with negative argument.
Some people have mental model that C is compiled on statement by
statement basis. In compiler that is doing simple statement by
statement compilation 'if' in 'foo2' would prevent call to
'foo4' with negative argument. If compiler simply replaced
'unreachable()' by a trap instruction, then program would
trap in ;foo1'. But undefined behaviour opens essentially
unlimied possibilites for program transformation, based on
surrouding code.
BTW: I can image code using a macro
#define ASSERT_FACT(x) {if (x) unreachable();}
and codebase sprinkled with statements like:
ASSERT_FACT(i >= 0 && i < 100)
So 'unreachable()' essentially allows passing arbitrary assertions
to the compiler.
Let me add that given enough facts compiler should be able to
prove that all of them are true. It would be nice to have an
option so that compiler warns if it can not prove that some
'unreachable()' is really unreachable.
And note that false statement implies everthing, so if your
'unreachable()' is wrong then there are unlimited possibilities
for unexpected behaviour.
How is this reasoning affected by the change in definition of
undefined behavior in C23 (as compared for example with the
definitions in C11 or C99)?
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
My personal Rust journey has been driven by a desire to match or
beat the fastest known times for the Advent of Code puzzles, from
2015 to 2025.
That's good to know but what is a good resource to learn the Rust
language? I've looked at the Rust book several times and each
time come away rather frustrated.
I don't have any suggestions here, I tend to google any questions I
have, then follow those links that point into the Rust std lib.
On 25/09/2026 07:39, Anton Ertl wrote:
David Brown <[email protected]> writes:
On 21/09/2026 19:17, Anton Ertl wrote:
I referred to a specific paradoxical effect, not completely avoiding
all of them: The effect of programmers who, instead of working on
making the code faster through source-level changes, have to invest
time into avoiding getting it miscompiled.
By "miscompiled", do you mean incorrect object code, or object code that >>> did not have the performance the programmer expected or hoped for?
Code that behaves differently than intended, e.g., where a bounds check
is "optimized" away.
The trouble with that is determining precisely what was intended.
Compilers are limited - the programmer has to express their intention in
the restricted form of the programming language (C or whatever else).
And the /only/ things that can be relied on to have a clear meaning are things that have clear defined behaviour as documented by the language standards or the compiler documentation.
A program is a contract between the programmer and the compiler - the programmer promises to provide good code with a clearly defined meaning,
and the compiler promises to give object code that implements that
defined meaning. If the programmer breaks their side of the contract by writing code that does not have a clear meaning (at compile-time or run-time), the compiler /cannot/ fulfil its side of the contract. It is unable to generate "correct" object code from incorrect source code.
If the programmer writes "a = b + c;" (all signed int), what is the intention? Does the programmer mean "generate an ADD assembly
instruction"? Does he/she mean "I don't care how it happens, but 'a'
should be the sum of 'b' and 'c'"? Do they mean "give me a two's
complement wrapped addition"? Do they mean "I promise there will be no overflow" ? Do they mean "I /hope/ there will be no overflow" ? Or
"While I am debugging, let me know if there is an overflow" ?
The programmer might have intended lots of subtly different things from
the same bit of code. But compilers cannot work by mind-reading. The
only thing that the /code/ intends is "Add these two numbers. I promise there will be no overflow". Whatever the programmer imagines, that is
the intention of the code. The compiler's job is to follow that - and
if the programmer breaks his promise, it is garbage in, garbage out.
I find the whole concept of optimising based on "what the programmer intended", rather than what the code they wrote means, somewhat hypocritical. The idea that "a = b + c;" should "obviously" wrap on a
two's complement machine, while "a = b + 10 + c - 10;" should
"obviously" optimise away the constants is clearly contradictory. It is
an attitude that is denying responsibility - claiming "the compiler
broke my code" rather than understanding "my code had mistakes but previously gave the right results by luck".
I am not underestimating the difficulty of writing correct code here, or
the frustration and costs involved when the behaviour of real-world code changes with different compilers or flags. But it is critical that programmers understand the origin of problems in order to deal with the issues instead of passing the blame around. Baring compiler bugs (even
my favourite compiler has one or two outstanding bugs), what you term "miscompiling" here is actually errors in the programmers' source code.
The way to improve the situation is not by blaming compiler writers, or inventing some kind of vague notion of what programmers "intended" or
"do what some old compiler did", or limiting optimisations in compilers.
Instead, it is by improving compilers, run-time checkers and static analysis tools to help identify the errors in the source code, improving teaching of programming to reduce the risk of such errors, improving languages to support lower-risk constructs, changing to programming languages with different characteristics, etc. Indeed, I find that
better compilers with more powerful optimisations means fewer bugs in my code because I can write the code in clearer ways and still get
efficient results in the end.
Performance regressions can be nasty for performance-sensitive code,
and consume time for finding a workaround (if one can be found), but
is a different thing.
Agreed. And correctness trumps speed every time.
But as
for the problems you mention w.r.t UB, I think it's just the result of >>>>> poor semantics, for which I guess we (language semanticists) are partly >>>>> to blame: we have developed fairly good tools to design sane language >>>>> specs in general,
I violently disagree. The C89 standard (just to name one) is a
partial specification not because the original C standards people were >>>> poor at specifying semantics, but because given the differences
between the compilers out there and the hardware out there, the
easiest way to reach a consensus is to leave some parts unspecified.
I have never spoken to the C standards committee or writers, either of
current standard versions or the original C89 standard, or writers of
pre-standard C specifications. So I cannot in any way claim to know
their thoughts or motivations. I also have not seen any documentation
that suggests what they might have thought about "optimisation on the
assumption that undefined behaviour does not occur" - in either
direction.
When C89 was developed, the dominating C compiler in the Unix world
was PCC. GCC came on the scene late in the game (1988), without such
assumptions and produced a significant performance advantage. Only
after several years of heavy gcc development assumptions like
-fstrict-aliasing were introduced around gcc-2.7 (which was initially
disabled by default);
("-fstrict-aliasing" is still not enabled by default - you have to
actively enable at least "-O2".)
I don't know when the assumption that signed
integer operations do not overflow was introduced, and initially it
could not be disabled (-fwrapv was added around gcc-3.4), so obviously
by that time people had taken over the gcc project who had a different
view about how undefined behaviour should be treated.
I completely disagree that this is an "obvious" conclusion. While I
cannot say anything for sure here, the explanation that comes first to
my mind is that it was part of the natural progression of gcc towards
more optimisations.
"-fwrapv" would have been added as a result of complaints about
problems with old code that made invalid assumptions that happened
to "work" because older gcc did not have as many optimisations.
I see absolutely no reason to assume that anyone at any time thought
such optimisations were not valid - they simply had not yet been
implemented.
David Brown <[email protected]> wrote:
On 25/09/2026 07:39, Anton Ertl wrote:
I don't know when the assumption that signed
integer operations do not overflow was introduced, and initially it
could not be disabled (-fwrapv was added around gcc-3.4), so obviously
by that time people had taken over the gcc project who had a different
view about how undefined behaviour should be treated.
I completely disagree that this is an "obvious" conclusion. While I
cannot say anything for sure here, the explanation that comes first to
my mind is that it was part of the natural progression of gcc towards
more optimisations.
"-fwrapv" would have been added as a result of complaints about
problems with old code that made invalid assumptions that happened
to "work" because older gcc did not have as many optimisations.
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it
was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv"
would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
In hindsight, though, it would have been easy enough to use unsigned arithmetic within GCC to support gcj.
I see absolutely no reason to assume that anyone at any time thought
such optimisations were not valid - they simply had not yet been
implemented.
I agree.
Andrew.
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it
was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv"
would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
[email protected]d <[email protected]d> schrieb:
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it
was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv"
would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
Linear congruential random number generators, in common use when
the dinosaurs roamed the computer centers, depend on wraparound
semantics. In gfortran, they stopped working at one time because
gcc assuming that this would not happen. -fwrap is a workaround
(without changing the source), but of course using the RANDOM_NUMBER intrinsic is better - it also uses a reasonable algorithm.
So, some people should probably be grateful to you :-)
On 04/10/2026 16:16, Thomas Koenig wrote:
[email protected]d <[email protected]d> schrieb:
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it
was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv"
would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
Linear congruential random number generators, in common use when
the dinosaurs roamed the computer centers, depend on wraparound
semantics. In gfortran, they stopped working at one time because
gcc assuming that this would not happen. -fwrap is a workaround
(without changing the source), but of course using the RANDOM_NUMBER
intrinsic is better - it also uses a reasonable algorithm.
So, some people should probably be grateful to you :-)
Do unsigned types in Fortran not have wrapping semantics? Or are there
no unsigned integer types in Fortran? I know not every language has the same distinction as C here.
David Brown <[email protected]> schrieb:
On 04/10/2026 16:16, Thomas Koenig wrote:
[email protected]d <[email protected]d> schrieb:
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it
was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv"
would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
Linear congruential random number generators, in common use when
the dinosaurs roamed the computer centers, depend on wraparound
semantics. In gfortran, they stopped working at one time because
gcc assuming that this would not happen. -fwrap is a workaround
(without changing the source), but of course using the RANDOM_NUMBER
intrinsic is better - it also uses a reasonable algorithm.
So, some people should probably be grateful to you :-)
Do unsigned types in Fortran not have wrapping semantics? Or are there
no unsigned integer types in Fortran? I know not every language has the
same distinction as C here.
The Fortran language has no unsigned types. Two compilers, gfortran
and flang, implement a suggestion that I made to J3 (much improved
by some people on the committee) and was accepted, but not taken
up by WG5. At the time, I had already started implementing it,
so I went ahead and completed it anyway, see
https://gcc.gnu.org/onlinedocs/gfortran/Unsigned-integers.html
(You can probably tell that this is still a sore point.)
But what the pseudo-random number generators did was use *signed*
overflow, which had been prohibited by the standard forever, but
because people needed something and it worked, they went ahead
and did it anyway.
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables. >>>>In Rust, the only acceptable array index is of type "usize", which is
effectively the same as u64 on most platforms these days, but does not >>>> need to be so: On a 32-bit target, it would be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such indices
are verified against the actual array upper boundary. [...]
It's nice that array index values are checked against the array
bounds.
But given that the checks are put in by the compiler, it seems
rather silly to insist that index expressions have type usize,
especially when a common remedy is just to add a cast. It's like
saying the car won't start unless you take off your seatbelt.
On Wed, 30 Sep 2026 13:57:24 -0000 (UTC)
[email protected] (Dan Cross) wrote:
In article <[email protected]>,
Michael S <[email protected]> wrote:
[snip]
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
Beginning Rust programmers tend to go through a few phases,
particularly if coming from a language like C. "Fighting the
Borrow Checker" is well-known; "traits are awesome; let's use
them everywhere!" is another.
I didn't reach the second stage. Not sure about first.
When I started programming in Rust, most of my immediate
colleagues and I had terribly little experience in the language.
We were excited about the its potential benefits, but realized
it would take a few months to build up a sufficient level of
familiarity to use it skillfully. One quipped, "this language
has a near-vertical learning curve."
IMHO, he is correct.
Rust is certainly the hardest to learn out of computer languges that I
ever tried that I would not call idiotic.
At one point a coworker (self-deprecatingly) said, "look, I'm
writing crust! C in Rust syntax!" Everyone thought this was a
great play on words, but he was correct: the programs we were
writing were essentially C programs, just expressed in Rust. We
quickly adopted that to describe our still naive code: it wasn't
an insult, just an expression that we were still coming to terms
with a new way of doing things. Some of the hallmarks of that
included using the wrong paradigms (and types!) for lots of our
code.
I'd be curious to know more about your program.
The program was an implementation scalable flood fill algorithm (i.e.
one with much better than typical worst case BigO properties, both for
speed and for space) together with test benches for correctness and
speed. As you could guess, I didn't really wrote it in Rust. It was a >non-literal port from C. The purpose of translation was two-fold:
improvement of my knowledge of Rust and getting better feel for cost of >mandatory bound checking for one particular sort of data structures.
Before doing Rust port I did port to Golang. Unlike for Rust, where I
only read The Book and went trough part of tutorial, I had small
previous practical experience in Go, but it was not very recent. Still,
the difference in my productivity was huge. In favor of Go, of course.
I've written
several hundred kloc of Rust at this point, and while what I
write may be terribly specialized, I rarely find myself indexing
an array; often, I'm using iterators or some other abstraction
for that kind of thing. If that was the one program you have
written in Rust, my _guess_ is that you were using paradigms
appropriate to a different language, which is why you felt you
needed to reach for casts to index an array so frequently.
Again, that's not a comment on your program, so much as
suggesting an impedence mismatch between what you wrote and how
one would generally expect to use the language.
In general, I think a person's experience writing a single
program tells us very little.
I never pretended otherwise.
Although I don't see how one can avoid
cast if one wants to use indices that originate in signed domain. Or,
as was often a case in this particular program, in domain of narrower >unsigned integers.
Dan Cross wrote:
In article <[email protected]>,
Michael S <[email protected]> wrote:
[snip]
My only Rust program was full of casts of various integer types to
usize. Exactly for the purpose of array indexing.
Beginning Rust programmers tend to go through a few phases,
particularly if coming from a language like C. "Fighting the
Borrow Checker" is well-known; "traits are awesome; let's use
them everywhere!" is another.
When I started programming in Rust, most of my immediate
colleagues and I had terribly little experience in the language.
We were excited about the its potential benefits, but realized
it would take a few months to build up a sufficient level of
familiarity to use it skillfully. One quipped, "this language
has a near-vertical learning curve."
At one point a coworker (self-deprecatingly) said, "look, I'm
writing crust! C in Rust syntax!" Everyone thought this was a
great play on words, but he was correct: the programs we were
writing were essentially C programs, just expressed in Rust. We
quickly adopted that to describe our still naive code: it wasn't
an insult, just an expression that we were still coming to terms
with a new way of doing things. Some of the hallmarks of that
included using the wrong paradigms (and types!) for lots of our
code.
I have definitely noted this in my own code, crust is a good name for it.
Quite often, when "fighting the borrow checker", typically because I'm >updating a complicated data structure in two locations at the same time, >I'll fall back to "write Fortran in Rust", with every access indexed.
This is BTW one of those problems the brand new upcoming borrow checker
is supposed to be far better at figuring out: If I'm updating totally >separate parts of the same larger structure, then it should be OK to
mutate both at the same time.
I do realize that there are far more Rusty ways to solve this type of
issue, they just don't come naturally to me yet.
Terje Mathisen <[email protected]> writes:
[on learning Rust]
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
(Please excuse my pursuing this non-arch topic...)
Question for any Rusters in the group --
What is the best was to learn the Rust programming language?
Which books or other documents are the best introductory or
reference materials?
Disclaimer: My experience with Rust is more than zero but
not a lot more than zero.
Tim Rentsch <[email protected]> writes:
That's good to know but what is a good resource to learn the Rust
language? I've looked at the Rust book several times and each
time come away rather frustrated.
If you mean "The Rust Programming Language", I came away from it >disillusioned.
The reason was that I could not get a search-tree insertion technique
to work that can be implemented in e.g., Pascal, C, and Forth, but not
in Java (because in Java I cannot produce references to a field inside
an object).
At some point I thought that one would have to use the more
heavy-weight reference-counting stuff to get through the borrow
checker.
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference >counting after all. However, the problem in this case is the usual
thing with type inference: There is so much going on that happens >automagically (in particular automatic dereferencing in Rust), and the
Rust compiler does not know where you went wrong, so the error message
is unhelpful (while for the simpler cases the compiler usually guesses
right and gives very helpful, to-the-point error messages).
Even after reading the second book, I am still disillusioned with
Rust. The Rust fans tend to say: "If it compiles, it runs." The
problem is that it can be harder to get a Rust program to compile than
to debug a program in, say, Forth (which has barely any checking).
For debugging, I at least have a method on how to proceed. When I get
an unhelpful compiler error, I am left to trying various things,
blindly.
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables. >>>>>In Rust, the only acceptable array index is of type "usize", which is >>>>> effectively the same as u64 on most platforms these days, but does not >>>>> need to be so: On a 32-bit target, it would be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such indices
are verified against the actual array upper boundary. [...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
But given that the checks are put in by the compiler, it seems
rather silly to insist that index expressions have type usize,
especially when a common remedy is just to add a cast. It's like
saying the car won't start unless you take off your seatbelt.
I don't know why you would say that. The point is that Rust is
strongly, statically typed and the type of an arrray index is
well-defined as `usize`. Being intentional in what one writes
is an important aspect of being a proficient Rust programmer.
[email protected] (Waldek Hebisch) writes:
Tim Rentsch <[email protected]> wrote:
Thomas Koenig <[email protected]> writes:
Bubblesort is the worst of non-joke sorting algorithms, see
the quote from Numerical Recipes...
For people who don't like bubble sort, or for inclusion in a set
of benchmarks, I suggest the following recently discovered
sorting algorithm (written in C-ish pseudocode):
void
baffle_sort( unsigned n, int *elements ){
for( unsigned i = 0; i < n; i++ ){
for( unsigned j = 0; j < n; j++ ){
if( elements[i] < elements[j] ){
/* exchange elements[i] and elements[j] */
}
}
}
}
(Disclaimer: not original.)
AFAICS this is very old "sorting by choice" algortim,
I suggest you look again. It is not a selection sort.
just written
in inefficient way (using swaps and keeping running max in the
array instead of using extra variable).
I am making no claim that this sort would be a good choice if
one wanted an efficient algorithm. Surely no one would choose
it on that basis. That's not what makes it interesting.
[email protected] (Waldek Hebisch) writes:
Tim Rentsch <[email protected]> wrote:
Thomas Koenig <[email protected]> writes:
Bubblesort is the worst of non-joke sorting algorithms, see
the quote from Numerical Recipes...
For people who don't like bubble sort, or for inclusion in a set
of benchmarks, I suggest the following recently discovered
sorting algorithm (written in C-ish pseudocode):
void
baffle_sort( unsigned n, int *elements ){
for( unsigned i = 0; i < n; i++ ){
for( unsigned j = 0; j < n; j++ ){
if( elements[i] < elements[j] ){
/* exchange elements[i] and elements[j] */
}
}
}
}
(Disclaimer: not original.)
AFAICS this is very old "sorting by choice" algortim,
I suggest you look again. It is not a selection sort.
just written
in inefficient way (using swaps and keeping running max in the
array instead of using extra variable).
I am making no claim that this sort would be a good choice if
one wanted an efficient algorithm. Surely no one would choose
it on that basis. That's not what makes it interesting.
On 04/10/2026 19:46, Thomas Koenig wrote:
David Brown <[email protected]> schrieb:
On 04/10/2026 16:16, Thomas Koenig wrote:
[email protected]d <[email protected]d> schrieb:
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it >>>>> was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv" >>>>> would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
Linear congruential random number generators, in common use when
the dinosaurs roamed the computer centers, depend on wraparound
semantics. In gfortran, they stopped working at one time because
gcc assuming that this would not happen. -fwrap is a workaround
(without changing the source), but of course using the RANDOM_NUMBER
intrinsic is better - it also uses a reasonable algorithm.
So, some people should probably be grateful to you :-)
Do unsigned types in Fortran not have wrapping semantics? Or are there
no unsigned integer types in Fortran? I know not every language has the >>> same distinction as C here.
The Fortran language has no unsigned types. Two compilers, gfortran
and flang, implement a suggestion that I made to J3 (much improved
by some people on the committee) and was accepted, but not taken
up by WG5. At the time, I had already started implementing it,
so I went ahead and completed it anyway, see
https://gcc.gnu.org/onlinedocs/gfortran/Unsigned-integers.html
(You can probably tell that this is still a sore point.)
But what the pseudo-random number generators did was use *signed*
overflow, which had been prohibited by the standard forever, but
because people needed something and it worked, they went ahead
and did it anyway.
I would guess you'd have trouble writing something that was efficient
here, and avoided UB when you don't have language support for wrapping overflow (whether it be for signed or unsigned types).
Tim Rentsch <[email protected]> wrote:...
[email protected] (Waldek Hebisch) writes:
Tim Rentsch <[email protected]> wrote:
Thomas Koenig <[email protected]> writes:
void
baffle_sort( unsigned n, int *elements ){
for( unsigned i = 0; i < n; i++ ){
for( unsigned j = 0; j < n; j++ ){
if( elements[i] < elements[j] ){
/* exchange elements[i] and elements[j] */
}
}
}
}
Probably the most interestiong thing about it is that it actually
sorts. As a benchmork it could be useful to assess quality of
compiler removal of useless code. I suspect that currently no
compiler is able to remove useless half of this code, also
probably can not optimize sequence of swaps to array shift. But
those are not far from what is alreade done.
David Brown <[email protected]> wrote:
On 04/10/2026 19:46, Thomas Koenig wrote:
David Brown <[email protected]> schrieb:
On 04/10/2026 16:16, Thomas Koenig wrote:
[email protected]d <[email protected]d> schrieb:
"-fwrapv" didn't exist when we started gcj, which used GCC to
implement Java. After some discussion with other GCC maintainers, it >>>>>> was agreed to add wrapping signed integer semantics to GCC's IR.
"-fwrapv" provides that to C-derived languages too. Whether "-fwrapv" >>>>>> would have happened without the pull from gcj I can't say. It's
impossible to rerun history.
Linear congruential random number generators, in common use when
the dinosaurs roamed the computer centers, depend on wraparound
semantics. In gfortran, they stopped working at one time because
gcc assuming that this would not happen. -fwrap is a workaround
(without changing the source), but of course using the RANDOM_NUMBER >>>>> intrinsic is better - it also uses a reasonable algorithm.
So, some people should probably be grateful to you :-)
Do unsigned types in Fortran not have wrapping semantics? Or are there >>>> no unsigned integer types in Fortran? I know not every language has the >>>> same distinction as C here.
The Fortran language has no unsigned types. Two compilers, gfortran
and flang, implement a suggestion that I made to J3 (much improved
by some people on the committee) and was accepted, but not taken
up by WG5. At the time, I had already started implementing it,
so I went ahead and completed it anyway, see
https://gcc.gnu.org/onlinedocs/gfortran/Unsigned-integers.html
(You can probably tell that this is still a sore point.)
But what the pseudo-random number generators did was use *signed*
overflow, which had been prohibited by the standard forever, but
because people needed something and it worked, they went ahead
and did it anyway.
I would guess you'd have trouble writing something that was efficient
here, and avoided UB when you don't have language support for wrapping
overflow (whether it be for signed or unsigned types).
If language supports large enough integers and with reasonable
optimizer code like:
bigint a = 123;
bigint b = 7;
bigint c = 17;
long f() {
a = 0xffffffffffffffff &(a*c + b);
return a;
}
will be compiled using wrapping 64-bit arithmetic. If additionaly
you make sure that a is local to random number module so compiler
knows that it has seen all updates to a, and updates stay within
64 bits than compiler can use 64-bit variables.
But having explicit wrapping types is more direct and easier to
implement, which mattered for dinosaurs.
In article <[email protected]>,
Anton Ertl <[email protected]> wrote:
Tim Rentsch <[email protected]> writes:
That's good to know but what is a good resource to learn the Rust >>>language? I've looked at the Rust book several times and each
time come away rather frustrated.
If you mean "The Rust Programming Language", I came away from it >>disillusioned.
The reason was that I could not get a search-tree insertion technique
to work that can be implemented in e.g., Pascal, C, and Forth, but not
in Java (because in Java I cannot produce references to a field inside
an object).
I think what you mean to say is that you couldn't find a way to
implement that in the manner you were accustomed to thinking
about the problem, given your prior experience.
But that is separate from whether it can be done or not.
In fact, in Rust you can use the approximately the same
techniques you would use in Pascal, C, or (probably) Forth: raw
pointers are a part of the language, and you can simply use them
with `unsafe` code, and it would be no more "unsafe" than in C
or Pascal. One might argue that that defeats the purpose of the
language, and you'd be right to a point. The difference is that
in C or Pascal, the entire program is what Rust would call
"unsafe", whereas generally this can be restricted to just a few
lines of carefully audited code in Rust.
FWIW, for things like containers of various kinds (trees, hash
tables, linked-lists, growable arrays, etc), production programs
usually use an existing library or crate instead of rolling
their own.
At some point I thought that one would have to use the more
heavy-weight reference-counting stuff to get through the borrow
checker.
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference >>counting after all. However, the problem in this case is the usual
thing with type inference: There is so much going on that happens >>automagically (in particular automatic dereferencing in Rust), and the
Rust compiler does not know where you went wrong, so the error message
is unhelpful (while for the simpler cases the compiler usually guesses >>right and gives very helpful, to-the-point error messages).
Even after reading the second book, I am still disillusioned with
Rust. The Rust fans tend to say: "If it compiles, it runs." The
problem is that it can be harder to get a Rust program to compile than
to debug a program in, say, Forth (which has barely any checking).
For debugging, I at least have a method on how to proceed. When I get
an unhelpful compiler error, I am left to trying various things,
blindly.
Sounds like you're in what we'd call the, "fighting the borrow
checker" stage. It may be that you end up really not liking
Rust or feeling that other languages are better, but I wager
that just about anyone who has seriously used the language has
gone through similar things.
Tim Rentsch <[email protected]> wrote:
[email protected] (Waldek Hebisch) writes:
Tim Rentsch <[email protected]> wrote:
Thomas Koenig <[email protected]> writes:
Bubblesort is the worst of non-joke sorting algorithms, see
the quote from Numerical Recipes...
For people who don't like bubble sort, or for inclusion in a set
of benchmarks, I suggest the following recently discovered
sorting algorithm (written in C-ish pseudocode):
void
baffle_sort( unsigned n, int *elements ){
for( unsigned i = 0; i < n; i++ ){
for( unsigned j = 0; j < n; j++ ){
if( elements[i] < elements[j] ){
/* exchange elements[i] and elements[j] */
}
}
}
}
(Disclaimer: not original.)
I am making no claim that this sort would be a good choice if
one wanted an efficient algorithm. Surely no one would choose
it on that basis. That's not what makes it interesting.
Probably the most interestiong thing about it is that it actually
sorts.
Tim Rentsch <[email protected]> writes:
That's good to know but what is a good resource to learn the Rust
language? I've looked at the Rust book several times and each
time come away rather frustrated.
If you mean "The Rust Programming Language", I came away from it disillusioned.
The reason was that I could not get a search-tree insertion technique
to work that can be implemented in e.g., Pascal, C, and Forth, but not
in Java (because in Java I cannot produce references to a field inside
an object).
At some point I thought that one would have to use the more
heavy-weight reference-counting stuff to get through the borrow
checker. [discovered later not to be necessary]
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference counting after all.
However, the problem in this case is the usual
thing with type inference: There is so much going on that happens automagically (in particular automatic dereferencing in Rust), and the
Rust compiler does not know where you went wrong, so the error message
is unhelpful (while for the simpler cases the compiler usually guesses
right and gives very helpful, to-the-point error messages).
Even after reading the second book, I am still disillusioned with
Rust. The Rust fans tend to say: "If it compiles, it runs." The
problem is that it can be harder to get a Rust program to compile than
to debug a program in, say, Forth (which has barely any checking).
For debugging, I at least have a method on how to proceed. When I get
an unhelpful compiler error, I am left to trying various things,
blindly.
The reason was that I could not get a search-tree insertion technique
to work that can be implemented in e.g., Pascal, C, and Forth, but not
in Java (because in Java I cannot produce references to a field inside
an object).
At some point I thought that one would have to use the more
heavy-weight reference-counting stuff to get through the borrow
checker. [discovered later not to be necessary]
I think this test case is a good one. Somewhat disappointing that
Rust makes it non-obvious. Do you know if what you learned would
carry over to, for example, AVL trees?
...Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference
counting after all.
Do you know if there is a PDF version somewhere rather than the >endless-series-of-webpages format shown on its website?
Even after reading the second book, I am still disillusioned with
Rust. The Rust fans tend to say: "If it compiles, it runs." The
problem is that it can be harder to get a Rust program to compile than
to debug a program in, say, Forth (which has barely any checking).
For debugging, I at least have a method on how to proceed. When I get
an unhelpful compiler error, I am left to trying various things,
blindly.
Can I ask how much experience you have with ML-style languages
(e.g., ML, OCaml, Haskell)? I'm wondering how the type inference in
Rust compares to the type inference done in those languages.
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables. >>>>>In Rust, the only acceptable array index is of type "usize", which is >>>>> effectively the same as u64 on most platforms these days, but does not >>>>> need to be so: On a 32-bit target, it would be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such indices
are verified against the actual array upper boundary. [...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
[email protected] (Anton Ertl) writes:
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference
counting after all.
Thank you for this reference. I am just starting to look it over.
Do you know if there is a PDF version somewhere rather than the >endless-series-of-webpages format shown on its website?
On Tue, 06 Oct 2026 05:01:32 -0700, Tim Rentsch
<[email protected]> wrote:
[email protected] (Anton Ertl) writes:
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference
counting after all.
Thank you for this reference. I am just starting to look it over.
Do you know if there is a PDF version somewhere rather than the
endless-series-of-webpages format shown on its website?
At the upper right are controls to go to the GIT repository or "print"
the book as rendered. BUt it seems that if you print/save you'll lose
the table of contents.
What I found most interesting is how it sorts or why it sorts. It
took me a while to understand what makes it work. It is difficult,
for example, to write invariants for this code that can be used to
establish the conclusion that sorting actually takes place.
Tim Rentsch <[email protected]> writes:
[email protected] (Anton Ertl) writes:
The reason was that I could not get a search-tree insertion technique
to work that can be implemented in e.g., Pascal, C, and Forth, but not
in Java (because in Java I cannot produce references to a field inside
an object).
At some point I thought that one would have to use the more
heavy-weight reference-counting stuff to get through the borrow
checker. [discovered later not to be necessary]
I think this test case is a good one. Somewhat disappointing that
Rust makes it non-obvious. Do you know if what you learned would
carry over to, for example, AVL trees?
What I learned? No. I did not really learn it. I saw how I could
modify an example from the book to achieve what I want, and understood
the explanation of that example, but I do not know how to use this
stuff to build code from scratch.
Whether it is possible to do AVL trees in safe Rust without resorting
to reference-counting and such? Maybe, but I guess there are too many mutable references around in AVL tree operations at the same time.
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference
counting after all.
Even after reading the second book, I am still disillusioned with
Rust. The Rust fans tend to say: "If it compiles, it runs." The
problem is that it can be harder to get a Rust program to compile than
to debug a program in, say, Forth (which has barely any checking).
For debugging, I at least have a method on how to proceed. When I get
an unhelpful compiler error, I am left to trying various things,
blindly.
Can I ask how much experience you have with ML-style languages
(e.g., ML, OCaml, Haskell)? I'm wondering how the type inference in
Rust compares to the type inference done in those languages.
A little with Miranda and Haskell (Hugs), and the latter is the reason
why I have this opinion of type inference: You get a long error
message that tells you that there is some inconsistency in the
inferred types; because the types are inferred from a significant
stretch of code, the problem could be anywhere in this code.
My impression is that in Rust you tend to nail down things more often
than in Haskell, so the stretches are shorter, but there is more
automagic going on in Rust, with automatic defererencing and
referencing, automatic dropping, etc., and the borrow checker
interacts with the automagic, which makes it at least as hard to deal
with as th Haskell type inference.
For Haskell I can at least make types explicit at various places to
reduce the area where the error might hide, but in Rust I have no way
to disable the automagic, and no way (or at least I don't know it) to
tell the borrow checker what I think it should be seeing, such that it
can tell me where my error is.
I have investigated some tool that IIRC rewrite the Rust program to
give more information about how it understands the program, but at
least for my test case, it was not enlightening.
As for the type system, a friend told me that he likes Rust because it
gives him what he likes about Haskell's type system in an imperative language.
Dan Cross wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables. >>>>>>In Rust, the only acceptable array index is of type "usize", which is >>>>>> effectively the same as u64 on most platforms these days, but does not >>>>>> need to be so: On a 32-bit target, it would be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such indices >>>> are verified against the actual array upper boundary. [...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
That's what I assumed, except I check my assumptions by godbolt'ing my
code, using -O -C "target_cpu=native" to get a bleeding edge optimized >build, and that's where I saw a loop which started with
while pos+7 < input.len() {
... here a bunch of accesses to input[p], input[p+1]...input[p+6]
}
Every one of those secodnary accesses were very obviously tested against
the input length (stored in a register for the duration)
I considered reporting this as a very clear performance bug!
[snip]
My impression is that in Rust you tend to nail down things more often
than in Haskell, so the stretches are shorter, but there is more
automagic going on in Rust, with automatic defererencing and
referencing, automatic dropping, etc., and the borrow checker
interacts with the automagic, which makes it at least as hard to deal
with as th Haskell type inference.
For Haskell I can at least make types explicit at various places to
reduce the area where the error might hide, but in Rust I have no way
to disable the automagic, and no way (or at least I don't know it) to
tell the borrow checker what I think it should be seeing, such that it
can tell me where my error is.
I have investigated some tool that IIRC rewrite the Rust program to
give more information about how it understands the program, but at
least for my test case, it was not enlightening.
As for the type system, a friend told me that he likes Rust because it
gives him what he likes about Haskell's type system in an imperative >language.
In article <11a3ofn$2ak3s$[email protected]>,
Terje Mathisen <[email protected]> wrote:
Dan Cross wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables. >>>>>>>In Rust, the only acceptable array index is of type "usize", which is >>>>>>> effectively the same as u64 on most platforms these days, but does not >>>>>>> need to be so: On a 32-bit target, it would be the same as u32. >>>>>>>
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such indices >>>>> are verified against the actual array upper boundary. [...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
That's what I assumed, except I check my assumptions by godbolt'ing my
code, using -O -C "target_cpu=native" to get a bleeding edge optimized
build, and that's where I saw a loop which started with
while pos+7 < input.len() {
... here a bunch of accesses to input[p], input[p+1]...input[p+6]
}
Every one of those secodnary accesses were very obviously tested against
the input length (stored in a register for the duration)
I considered reporting this as a very clear performance bug!
I wonder if what you saw was actually loop unrolling. I just
tried something similar, albeit with a very simple sum in the
loop body, and don't see a bunch of extraneous bounds checks.
I do wonder, though, whether you'd get better object code using `input.iter().chunks(n)` or `.windows(n)`.
In article <[email protected]>,
Anton Ertl <[email protected]> wrote:
Tim Rentsch <[email protected]> writes:
That's good to know but what is a good resource to learn the Rust >>>>language? I've looked at the Rust book several times and each
time come away rather frustrated.
If you mean "The Rust Programming Language", I came away from it >>>disillusioned.
The reason was that I could not get a search-tree insertion technique
to work that can be implemented in e.g., Pascal, C, and Forth, but not
in Java (because in Java I cannot produce references to a field inside
an object).
I think what you mean to say is that you couldn't find a way to
implement that in the manner you were accustomed to thinking
about the problem, given your prior experience.
My goal was to implement search tree insertion as efficiently and
compactly as in Pascal, C or Forth (and in contrast to Java), ideally
in safe Rust. Here it is in C (untested):
struct tree {
int key; /* or some other key type */
int val; /* or some other value type */
struct tree *left;
struct tree *right;
};
struct tree **search(struct tree **rootp, int key)
{
while ((root=*rootp)!=NULL && key != root->key) {
if (key < root->key)
rootp = &(root->left);
else
rootp = &(root->right);
}
return rootp;
}
void insert(struct tree **rootp, int key, int val)
{
struct tree **nodep = **search(rootp, key);
struct tree *node = *nodep;
if (node == NULL) {
node = malloc(sizeof(tree));
node->key = key;
node->left = NULL;
node->right = NULL;
*nodep = node;
}
node->val = val;
}
Passing a reference to a mutable field is what is done in these
languages, and I don't know an efficient and similarly compact
technique that does not pass a reference to a mutable field.
I also knew Java solutions to the problem:
1) Special-case the root (which you typically have to do in Java
anyway, and otherwise always keep a pointer to the last tree node
around, and also keep the knowledge about the last left-or-right
decision around (or take it again). This all leads to quite a bit of
code that is duplicated relative to the code above.
2) Recurse into the tree and rewrite all the links to the subtrees
along the spine of the search. This is not quite as duplicative as 1)
(but the root still has to be handled separately), but it performs
several writes instead of just one, and the recursion is not a tail >recursion, so is relatively expensive.
BEGIN `tree.rs`<---//! Simple binary search tree.
END `tree.rs`<---
I wanted something with the benefits of the Pascal/C/Forth variant,
not something like the Java variants. I don't know if that counts as
"manner you were accustomed to thinking about the problem".
And I learned this technique in Pascal or Modula-2 (probably from one
of Wirth's books), and had no problem transferring the knowledge into
C or Forth, on the contrary: In C and Forth I could turn search into a
loop (in Pascal it has to be a tail-recursive function, because you
can only produce references to variables and fields through reference >parameters).
For a language that aspires to take over from C for systems
programming, I expect that one can use this technique, too.
BEGIN `unsafe_tree.rs`<---//! Simple binary search tree.
END `unsafe_tree.rs`<---
But that is separate from whether it can be done or not.
Sure, and as I found out later, it can be done.
In fact, in Rust you can use approximately the same
techniques you would use in Pascal, C, or (probably) Forth: raw
pointers are a part of the language, and you can simply use them
with `unsafe` code, and it would be no more "unsafe" than in C
or Pascal. One might argue that that defeats the purpose of the
language, and you'd be right to a point. The difference is that
in C or Pascal, the entire program is what Rust would call
"unsafe", whereas generally this can be restricted to just a few
lines of carefully audited code in Rust.
Given that I do not understand the automagic stuff done by the Rust
compiler enough to get this kind of tree insertion past the borrow
checker, even though it is possible, I don't trust my capabilities to
audit unsafe Rust.
FWIW, for things like containers of various kinds (trees, hash
tables, linked-lists, growable arrays, etc), production programs
usually use an existing library or crate instead of rolling
their own.
When I saw that this technique cannot be implemented in Java, I
wondered why Java was not having references to fields (answer:
probably because that would make garbage collection more costly and/or
less precise), that this technique is needed for relatively few
things, mostly stuff that's in libraries. And I wondered how these
library methos are implemented (at one point I guessed that they
implemented these parts of their libraries in C, but when I actually
look, the implementation of what I looked at was in Java, IIRC along
the lines of 1)).
For a language that aspires to replace C for systems programming,
giving the Java answer is not good enough, though.
At some point I thought that one would have to use the more
heavy-weight reference-counting stuff to get through the borrow
checker.
Then I found the book "Learn Rust With Entirely Too Many Linked
Lists", and I found that the problem could be solved without reference >>>counting after all. However, the problem in this case is the usual
thing with type inference: There is so much going on that happens >>>automagically (in particular automatic dereferencing in Rust), and the >>>Rust compiler does not know where you went wrong, so the error message
is unhelpful (while for the simpler cases the compiler usually guesses >>>right and gives very helpful, to-the-point error messages).
Even after reading the second book, I am still disillusioned with
Rust. The Rust fans tend to say: "If it compiles, it runs." The
problem is that it can be harder to get a Rust program to compile than
to debug a program in, say, Forth (which has barely any checking).
For debugging, I at least have a method on how to proceed. When I get
an unhelpful compiler error, I am left to trying various things,
blindly.
Sounds like you're in what we'd call the, "fighting the borrow
checker" stage. It may be that you end up really not liking
Rust or feeling that other languages are better, but I wager
that just about anyone who has seriously used the language has
gone through similar things.
I wonder about that.
From the books I read, I get the impression that
Rust used to have less automagic stuff, and much of the automagic was
added later, as a convenience for the programmers who already knew the >language. So the Rust programmers of the early days formed a mental
model based on "Pedestrian Rust" (i.e., with less automagic), and when
they see a program with more automagic, and need to understand some >conveniently short piece of Rust code in detail, they form a mental
model of the code in Pedestrian Rust. But how can a Rust programmer
who starts now learn Pedestrian Rust and how it relates to the modern >language. There are hints in the books I read, but not enough to get
a firm foundation.
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index
variables.
In Rust, the only acceptable array index is of type "usize",
which is effectively the same as u64 on most platforms these
days, but does not need to be so: On a 32-bit target, it would
be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such
indices are verified against the actual array upper boundary.
[...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
Dan Cross wrote:
In article <11a3ofn$2ak3s$[email protected]>,
Terje Mathisen <[email protected]> wrote:
Dan Cross wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index variables. >>>>>>>>In Rust, the only acceptable array index is of type "usize", which is >>>>>>>> effectively the same as u64 on most platforms these days, but does not >>>>>>>> need to be so: On a 32-bit target, it would be the same as u32. >>>>>>>>
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of >>>>>>> the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial >>>>>> underpinnings of Rust safety. Even more importantly, all such indices >>>>>> are verified against the actual array upper boundary. [...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
That's what I assumed, except I check my assumptions by godbolt'ing my
code, using -O -C "target_cpu=native" to get a bleeding edge optimized
build, and that's where I saw a loop which started with
while pos+7 < input.len() {
... here a bunch of accesses to input[p], input[p+1]...input[p+6]
}
Every one of those secodnary accesses were very obviously tested against >>> the input length (stored in a register for the duration)
I considered reporting this as a very clear performance bug!
I wonder if what you saw was actually loop unrolling. I just
tried something similar, albeit with a very simple sum in the
loop body, and don't see a bunch of extraneous bounds checks.
Not unrolling, the code is here:
https://godbolt.org/z/YnY5G1K49
The interesting part starts at line 28/29 .LBB0_02 in the asm window
where you can see (starting at line 37) a bunch of cmp r8,rsi / jae
<panic> sequences.
I do wonder, though, whether you'd get better object code using
`input.iter().chunks(n)` or `.windows(n)`.
I guess I can try this, I'm guessing chunks(7) will error out if less
than 7 elements are available?
The code is from Advent of Code 2024 day 3, >https://github.com/TerjeWiigMathisen/aoc/tree/master/2024/day03
BEGIN<---pub fn next(mut s: &str) -> Option<(i32, &str)> {
END<---
On Mon, 5 Oct 2026 11:36:59 -0000 (UTC)
[email protected] (Dan Cross) wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index
variables.
In Rust, the only acceptable array index is of type "usize",
which is effectively the same as u64 on most platforms these
days, but does not need to be so: On a 32-bit target, it would
be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such
indices are verified against the actual array upper boundary.
[...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
Are you sure?
Methinks The Book states otherwise: >https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
In article <[email protected]>,
Michael S <[email protected]> wrote:
On Mon, 5 Oct 2026 11:36:59 -0000 (UTC)
[email protected] (Dan Cross) wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index
variables.
In Rust, the only acceptable array index is of type "usize",
which is effectively the same as u64 on most platforms these
days, but does not need to be so: On a 32-bit target, it
would be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99%
of the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more
crucial underpinnings of Rust safety. Even more importantly,
all such indices are verified against the actual array upper
boundary. [...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
Are you sure?
Methinks The Book states otherwise: >https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
If the array length and index are known ahead of time, or can be
statically proven to be valid, then yes, the checks are (likely)
removed in release mode as part of the ordinary optimization
passes.
In the case where one has already asserted that
`p + n < arr.len()`, then I would expect any index `arr[p + k]`
to omit the bounds check, for k < n.
- Dan C.
In article <11a5lqo$2v5bq$[email protected]>,
Terje Mathisen <[email protected]> wrote:
Not unrolling, the code is here:
https://godbolt.org/z/YnY5G1K49
The interesting part starts at line 28/29 .LBB0_02 in the asm window
where you can see (starting at line 37) a bunch of cmp r8,rsi / jae
<panic> sequences.
So you're not actually indexing into an _array_ here, but rather
into a _slice_, which is always bounds-checked in safe Rust
code.
I do wonder, though, whether you'd get better object code using
`input.iter().chunks(n)` or `.windows(n)`.
I guess I can try this, I'm guessing chunks(7) will error out if less
than 7 elements are available?
But really what you want is to find all subsequences matching
the regular expression , `mul([0-9]+,[0-9]+)`, and then extract
the two numeric fields, multiple them, and sum the results.
That's a reduction over a sequence of products, which is itself
a reduction over a sequence of patches that yield pairs of
integers.
I understand you want raw speed; a way to get that is to
implement the DFA corresponding to that regexp by hand, and use
something like Boyer Moore to handle the fixed part.
The code is from Advent of Code 2024 day 3,
https://github.com/TerjeWiigMathisen/aoc/tree/master/2024/day03
Oh, I don't do Advent of Code, but looking briefly at the
problem description I don't see why you couldn't use `&str` to
represent the input data. Nothing says it's not UTF-8, and from
what I understand, the guy who runs it usually isn't a jerk
about that kind of thing. I haven't tried to benchmark your
code against, e.g., this:
BEGIN<---pub fn next(mut s: &str) -> Option<(i32, &str)> {
while !s.is_empty() {
let pos = s.find("mul(")?;
let (_, rest) = s.split_at(pos + 4);
s = rest;
let comma = s.find(|c: char| !c.is_ascii_digit())?;
let (adigits, rest) = s.split_at(comma);
let Some(rest) = rest.strip_prefix(",") else {
s = rest;
continue;
};
let a = adigits.parse::<i32>().ok()?;
let rparen = rest.find(|c: char| !c.is_ascii_digit())?;
let (bdigits, rest) = rest.split_at(rparen);
let Some(rest) = rest.strip_prefix(")") else {
s = rest;
continue;
};
let b = bdigits.parse::<i32>().ok()?;
return Some((a * b, rest));
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalids() {
assert_eq!(next("aaa"), None);
assert_eq!(next(""), None);
assert_eq!(next("mul(3,5"), None);
assert_eq!(next("mul(3, 5"), None);
assert_eq!(next("mul(3,"), None);
assert_eq!(next("mul(3"), None);
assert_eq!(next("mul("), None);
}
#[test]
fn aproduct() {
let s = "abmul[1,2]mul(3,5)a";
assert_eq!(next(s), Some((15, "a")));
}
#[test]
fn mproducts() {
let s = "mul(1,2)mul(3,4)";
let aopt = next(s);
assert_eq!(aopt, Some((2, "mul(3,4)")));
let (a, s) = aopt.unwrap();
let bopt = next(s);
assert_eq!(bopt, Some((12, "")));
let (b, s) = bopt.unwrap();
assert_eq!(next(s), None);
assert_eq!(a * b, 24);
}
}
END<---
I suspect yours may be somewhat faster, though this likely does
more error checking. YMMV.
In article <[email protected]>,
Michael S <[email protected]> wrote:
On Mon, 5 Oct 2026 11:36:59 -0000 (UTC)
[email protected] (Dan Cross) wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index
variables.
In Rust, the only acceptable array index is of type "usize",
which is effectively the same as u64 on most platforms these
days, but does not need to be so: On a 32-bit target, it would
be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such
indices are verified against the actual array upper boundary.
[...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
Are you sure?
Methinks The Book states otherwise:
https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
If the array length and index are known ahead of time, or can be
statically proven to be valid, then yes, the checks are (likely)
removed in release mode as part of the ordinary optimization
passes. In the case where one has already asserted that
`p + n < arr.len()`, then I would expect any index `arr[p + k]`
to omit the bounds check, for k < n.
https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
On 2026-Oct-07 15:25, Michael S wrote:
https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
Does Rust allow zero length arrays, vectors, strings and slices?
Ada calls these a null slice.
In DEC Fortran 77 a zero length slice was a bounds check error,
which was really irritating because in string manipulation they happen a lot. If so, how does one specify a null slice in Rust?
I couldn't find any definition in that doc.
On 2026-Oct-07 15:25, Michael S wrote:
https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
Does Rust allow zero length arrays, vectors, strings and slices?
Ada calls these a null slice.
In DEC Fortran 77 a zero length slice was a bounds check error,
On Mon, 5 Oct 2026 11:36:59 -0000 (UTC)
[email protected] (Dan Cross) wrote:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:[...]
Terje Mathisen <[email protected]> writes:
Tim Rentsch wrote:
I favor a type equivalent to size_t for use as array index
variables.
In Rust, the only acceptable array index is of type "usize",
which is effectively the same as u64 on most platforms these
days, but does not need to be so: On a 32-bit target, it would
be the same as u32.
So effectively very similar to size_t.
Maybe that limitation works okay in Rust. In C sometimes it's
important to allow a signed value for indexing. Probably 99% of
the time size_t is good, but not always.
The fact that array indices are unsigned is one of the more crucial
underpinnings of Rust safety. Even more importantly, all such
indices are verified against the actual array upper boundary.
[...]
It's nice that array index values are checked against the array
bounds.
Terje was actually incorrect here; bounds checks are added in
debug mode, but are (usually) removed in optimized builds.
Are you sure?
Methinks The Book states otherwise: https://doc.rust-lang.org/book/ch03-02-data-types.html?highlight=array#invalid-array-element-access
Dan Cross wrote:
In article <11a5lqo$2v5bq$[email protected]>,
Terje Mathisen <[email protected]> wrote:
Not unrolling, the code is here:
https://godbolt.org/z/YnY5G1K49
The interesting part starts at line 28/29 .LBB0_02 in the asm window
where you can see (starting at line 37) a bunch of cmp r8,rsi / jae
<panic> sequences.
So you're not actually indexing into an _array_ here, but rather
into a _slice_, which is always bounds-checked in safe Rust
code.
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable >array.
Even if it isn't, the fact that I start by verifying that the slice
extends at least 7 bytes past the p index means that all of p,
p+1,p+2...p+6 must be valid, right?
I do wonder, though, whether you'd get better object code using
`input.iter().chunks(n)` or `.windows(n)`.
I guess I can try this, I'm guessing chunks(7) will error out if less
than 7 elements are available?
But really what you want is to find all subsequences matching
the regular expression , `mul([0-9]+,[0-9]+)`, and then extract
the two numeric fields, multiple them, and sum the results.
That's a reduction over a sequence of products, which is itself
a reduction over a sequence of patches that yield pairs of
integers.
Yes, doing it that way is easy!
[snip]
represent the input data. Nothing says it's not UTF-8, and from
what I understand, the guy who runs it usually isn't a jerk
about that kind of thing. I haven't tried to benchmark your
code against, e.g., this:
What you implement here is just Part1: After solving that you get to
read Part2 where you have to also detect "do()" and "don't()" sequences,
so that the multiplication result only adds to the part2 result if we're
not following a don't().
[snip]
I suspect yours may be somewhat faster, though this likely does
more error checking. YMMV.
My guess is that your code (after adding the simultaneous scans for the >do/don't markers) will take about twice as long (or more?) as the 7 us
both my own and u/maneatingape (reddit handle of the guy with the
fastest Rust solutions to all the 500+ puzzles) needs to deliver the result.
[snip]
Setting up a combined Boyer-Moore would be doable:
If the fourth byte is '(', ')' or "'", check the preceeding bytes, if
not a match use a pre-computed skip table (maybe even one for each
initial match?) to increment the pointer. Hmmm... maybe I'll try this!
Dan Cross wrote:
[snip]
If the array length and index are known ahead of time, or can be
statically proven to be valid, then yes, the checks are (likely)
removed in release mode as part of the ordinary optimization
passes. In the case where one has already asserted that
`p + n < arr.len()`, then I would expect any index `arr[p + k]`
to omit the bounds check, for k < n.
That's exactly what I expected when I wrote my code the way I did. I
know that Java specifically handles this situation, lifting any array
bounds checking out of inner loops if possible.
As that Godbolt link shows, my expectation was wrong.
OTOH, for such a small program having 6 extra branches, none of them
ever taken, is not likely to add any cycles to the program latency since >current CPUs have more than enough issue ports to be able to handle
these branches while processing the real code.
[snip]
The reference is less see-spot-run
https://doc.rust-lang.org/stable/reference/
Looking at the rules for type casting:
https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#numeric-cast
Some initial thoughts:
There are no value range checks between cast types, just bit-wise reinterpretation
to the new type with possible sign/zero extension or truncation for size change.
They don't define the order in which those two operations, type and size change, occur.
If one casts an i8 into a u16 it doesn't say whether the value is sign extended
to i16 first then type changed to u16, or type changed to u8 first then zero >extended to u16.
Since an array index is defined as type usize, if the index expression
is signed then it will be bit-wise reinterpreted as unsigned,
and possibly turn a negative value into a large positive usize
value.
If bounds checks are disabled for
production builds then you get exactly the same array overflow as in C.
In article <11a6agf$37m70$[email protected]>,
Terje Mathisen <[email protected]> wrote:
Dan Cross wrote:
[snip]
If the array length and index are known ahead of time, or can be
statically proven to be valid, then yes, the checks are (likely)
removed in release mode as part of the ordinary optimization
passes. In the case where one has already asserted that
`p + n < arr.len()`, then I would expect any index `arr[p + k]`
to omit the bounds check, for k < n.
That's exactly what I expected when I wrote my code the way I did. I
know that Java specifically handles this situation, lifting any array
bounds checking out of inner loops if possible.
As that Godbolt link shows, my expectation was wrong.
OTOH, for such a small program having 6 extra branches, none of them
ever taken, is not likely to add any cycles to the program latency since
current CPUs have more than enough issue ports to be able to handle
these branches while processing the real code.
I thought it was worth a small experiment; here's a silly pair
of functions:
```rust
#[inline(never)]
pub fn sum_sub_array(arr: [u32; 4], k: usize) -> u32 {
if k + 3 > arr.len() {
return 0;
}
arr[k] + arr[k + 1] + arr[k + 2]
}
#[inline(never)]
pub fn sum_sub_slice(sl: &[u32], k: usize) -> u32 {
if k + 3 > sl.len() {
return 0;
}
sl[k] + sl[k + 1] + sl[k + 2]
}
```
If I run these in such a way that they're not totally optimized
away and do a release build, they generate the exact same code:
```
0000000000015cb0 <rust_indices::sum_sub_array>:
15cb0: 31 c0 xor %eax,%eax
15cb2: 48 83 fe 01 cmp $0x1,%rsi
15cb6: 77 0b ja 15cc3 <rust_indices::sum_sub_array+0x13>
15cb8: 8b 44 b7 04 mov 0x4(%rdi,%rsi,4),%eax
15cbc: 03 04 b7 add (%rdi,%rsi,4),%eax
15cbf: 03 44 b7 08 add 0x8(%rdi,%rsi,4),%eax
15cc3: c3 ret
15cc4: cc int3
...
15ccf: cc int3
0000000000015cd0 <rust_indices::sum_sub_slice>:
15cd0: 31 c0 xor %eax,%eax
15cd2: 48 83 fe 01 cmp $0x1,%rsi
15cd6: 77 0b ja 15ce3 <rust_indices::sum_sub_slice+0x13>
15cd8: 8b 44 b7 04 mov 0x4(%rdi,%rsi,4),%eax
15cdc: 03 04 b7 add (%rdi,%rsi,4),%eax
15cdf: 03 44 b7 08 add 0x8(%rdi,%rsi,4),%eax
15ce3: c3 ret
15ce4: cc int3
...
15cef: cc int3
```
Perhaps it's something about the complexity of your function
that exceed some sort of threshold for the optimizer.
[Crossposted and and Followup-To: comp.programming]
In article <[email protected]>,
Anton Ertl <[email protected]> wrote:
My goal was to implement search tree insertion as efficiently and
compactly as in Pascal, C or Forth (and in contrast to Java), ideally
in safe Rust. Here it is in C (untested):
struct tree {
int key; /* or some other key type */
int val; /* or some other value type */
struct tree *left;
struct tree *right;
};
struct tree **search(struct tree **rootp, int key)
{
while ((root=*rootp)!=NULL && key != root->key) {
if (key < root->key)
rootp = &(root->left);
else
rootp = &(root->right);
}
return rootp;
}
An intersting way to write it; I understand you're trying to
reuse `search` in `insert`, below, and thus why you want to
return `rootp`.
void insert(struct tree **rootp, int key, int val)
{
struct tree **nodep = **search(rootp, key);
I know you said this was untested; clearly you should not
indirect the result of `search` here.
struct tree *node = *nodep;
if (node == NULL) {
node = malloc(sizeof(tree));
node->key = key;
node->left = NULL;
node->right = NULL;
*nodep = node;
}
node->val = val;
}
Passing a reference to a mutable field is what is done in these
languages, and I don't know an efficient and similarly compact
technique that does not pass a reference to a mutable field.
I also knew Java solutions to the problem:
1) Special-case the root (which you typically have to do in Java
anyway, and otherwise always keep a pointer to the last tree node
around, and also keep the knowledge about the last left-or-right
decision around (or take it again). This all leads to quite a bit of
code that is duplicated relative to the code above.
2) Recurse into the tree and rewrite all the links to the subtrees
along the spine of the search. This is not quite as duplicative as 1)
(but the root still has to be handled separately), but it performs
several writes instead of just one, and the recursion is not a tail >>recursion, so is relatively expensive.
Or, 3) Simply reimplement the comparison in `insert`
it's a trivial amount of code.
I chose a variant of (1) for my implementation, but only because
it was easy, not because it was required. No unsafe code or
fancy reference counting here.
BEGIN `tree.rs`<---//! Simple binary search tree.
use std::cmp::Ordering;
use std::fmt::Debug;
/// A binary search tree over any key type that implements a
/// total ordering.
pub struct Tree<K: Debug + Ord, V: Debug> {
root: Option<Box<Node<K, V>>>,
}
impl<K: Debug + Ord, V: Debug> Tree<K, V> {
/// Creates a new, empty tree.
pub fn new() -> Tree<K, V> {
Tree { root: None }
}
/// Searches the tree for the given key, and returns
/// `Some(&value)` if it's here, otherwise returns None.
///
/// # Example
///
/// ```
/// let mut tree = tree::Tree::<i32, i32>::new();
/// tree.insert(2, 200);
/// tree.insert(1, 100);
/// assert_eq!(tree.search(&0), None);
/// assert_eq!(tree.search(&1), Some(&100));
/// assert_eq!(tree.search(&2), Some(&200));
/// ```
pub fn search(&self, key: &K) -> Option<&V> {
let mut root = &self.root;
while let Some(node) = root {
match key.cmp(&node.key) {
Ordering::Less => root = &node.left,
Ordering::Equal => return Some(&node.value),
Ordering::Greater => root = &node.right,
}
}
None
}
/// Inserts the given `value` into the tree with the given
/// `key`. Returns true if the node was inserted, or false
/// if the key was already in the tree.
///
/// # Example
///
/// ```
/// let mut tree = tree::Tree::<i32, i32>::new();
/// assert_eq!(tree.insert(1, 100), true);
/// assert_eq!(tree.insert(1, 200), false);
/// assert_eq!(tree.insert(2, 100), true);
/// ```
pub fn insert(&mut self, key: K, value: V) -> bool {
let mut root = &mut self.root;
loop {
let Some(node) = root else {
root.replace(Node::new(key, value));
return true;
};
match key.cmp(&node.key) {
Ordering::Less => root = &mut node.left,
Ordering::Equal => return false,
Ordering::Greater => root = &mut node.right,
}
}
}
/// Print the tree, if it is not empty.
pub fn print(&self) {
if let Some(node) = &self.root {
node.print();
}
}
}
// A node in the tree owns its key, value, and left and right
// children.
struct Node<K: Debug + Ord, V: Debug> {
key: K,
value: V,
left: Option<Box<Node<K, V>>>,
right: Option<Box<Node<K, V>>>,
}
impl<K: Debug + Ord, V: Debug> Node<K, V> {
/// Creates a new boxed node owning the given key and value.
pub fn new(key: K, value: V) -> Box<Node<K, V>> {
Box::new(Node {
key,
value,
left: None,
right: None,
})
}
/// Pre-order print of this node and its children.
pub fn print(&self) {
if let Some(left) = &self.left {
left.print();
}
println!("key = {:?}, value = {:?}", self.key, self.value);
if let Some(right) = &self.right {
right.print();
}
}
}
END `tree.rs`<---
Note that there's more code here than in your C code, but this
also does a lot more; this is generic over any key and value
types where the key type implements a total ordering, and where
both types implement the `Debug` trait (so they can be printed).
though the pointer-to-pointer
to a node is not returned by `search`. If that is what you mean
by "technique" then you're right; doing that in Rust is
inconvenient because it violates the borrow checker's rules
about the existence of multiple simultaneous mutable references
to an object.
But I claim that is just a detail, and not a
terribly interesting one at that; certainly not an impediment to
substituting Rust for C in systems programming.
Even so, as I said, one _can_ use the same technique using a
smallish-bit of `unsafe` code.
Frankly, unless I was doing something very odd, I wouldn't write
my own binary search tree at all; the standard library's
collections implementations are pretty good
Given that I do not understand the automagic stuff done by the Rust >>compiler enough to get this kind of tree insertion past the borrow
checker, even though it is possible, I don't trust my capabilities to
audit unsafe Rust.
Is it significantly different than doing so in C, Pascal, or
Forth? See the second program, above; the use of `unsafe` in
the bits that correspond to your sketch in C is constrained to
two places that indirect raw pointers.
It's so common that "fighting the borrow checker" is the
community's slogan to describe it.
From the books I read, I get the impression that
Rust used to have less automagic stuff, and much of the automagic was
added later, as a convenience for the programmers who already knew the >>language. So the Rust programmers of the early days formed a mental
model based on "Pedestrian Rust" (i.e., with less automagic), and when
they see a program with more automagic, and need to understand some >>conveniently short piece of Rust code in detail, they form a mental
model of the code in Pedestrian Rust. But how can a Rust programmer
who starts now learn Pedestrian Rust and how it relates to the modern >>language. There are hints in the books I read, but not enough to get
a firm foundation.
Early Rust was very different, and much closer to a managed
language with garbage collection, green threads, a heavy-weight
runtime, etc. Put another way, the trend has been less and less
"automagic" (as you put it) and making things more explicit over
time.
You keep conflating things you are not familiar with in a
language you are not familiar with, with the idea that those
things are "automagic." But really there is no magic here, and
I suspect the answer to your question about how one learns to
adapt their mental model of Rust is to first stop thinking of it
as magical.
[Crossposted and and Followup-To: comp.programming]
Given that I don't subscribe to c.p, I kept comp.arch in the
Newsgroups.
In article <[email protected]>,
Anton Ertl <[email protected]> wrote:
My goal was to implement search tree insertion as efficiently and >>>compactly as in Pascal, C or Forth (and in contrast to Java), ideally
in safe Rust. Here it is in C (untested):
struct tree {
int key; /* or some other key type */
int val; /* or some other value type */
struct tree *left;
struct tree *right;
};
struct tree **search(struct tree **rootp, int key)
{
while ((root=*rootp)!=NULL && key != root->key) {
if (key < root->key)
rootp = &(root->left);
else
rootp = &(root->right);
}
return rootp;
}
An intersting way to write it; I understand you're trying to
reuse `search` in `insert`, below, and thus why you want to
return `rootp`.
void insert(struct tree **rootp, int key, int val)
{
struct tree **nodep = **search(rootp, key);
I know you said this was untested; clearly you should not
indirect the result of `search` here.
struct tree *node = *nodep;
if (node == NULL) {
node = malloc(sizeof(tree));
node->key = key;
node->left = NULL;
node->right = NULL;
*nodep = node;
}
node->val = val;
}
Passing a reference to a mutable field is what is done in these >>>languages, and I don't know an efficient and similarly compact
technique that does not pass a reference to a mutable field.
I also knew Java solutions to the problem:
1) Special-case the root (which you typically have to do in Java
anyway, and otherwise always keep a pointer to the last tree node
around, and also keep the knowledge about the last left-or-right
decision around (or take it again). This all leads to quite a bit of >>>code that is duplicated relative to the code above.
2) Recurse into the tree and rewrite all the links to the subtrees
along the spine of the search. This is not quite as duplicative as 1) >>>(but the root still has to be handled separately), but it performs >>>several writes instead of just one, and the recursion is not a tail >>>recursion, so is relatively expensive.
Or, 3) Simply reimplement the comparison in `insert`
That goes along with 1), but I forgot to mention it.
it's a trivial amount of code.
There's the rub. I don't consider a factor of ~2 trivial, even if it
is only 20 lines of code.
And in particular, "simply reimplement"
means that the same thing occurs several times in the code, and in >maintainance one must remember to change all of the occurences.
Fortunately, that's not necessary in Rust. But the fact that you did
not find it tells me that Rust is hard even for experienced Rust
programmers.
I chose a variant of (1) for my implementation, but only because
it was easy, not because it was required. No unsafe code or
fancy reference counting here.
BEGIN `tree.rs`<---//! Simple binary search tree.
use std::cmp::Ordering;
use std::fmt::Debug;
/// A binary search tree over any key type that implements a
/// total ordering.
pub struct Tree<K: Debug + Ord, V: Debug> {
root: Option<Box<Node<K, V>>>,
}
impl<K: Debug + Ord, V: Debug> Tree<K, V> {
/// Creates a new, empty tree.
pub fn new() -> Tree<K, V> {
Tree { root: None }
}
/// Searches the tree for the given key, and returns
/// `Some(&value)` if it's here, otherwise returns None.
///
/// # Example
///
/// ```
/// let mut tree = tree::Tree::<i32, i32>::new();
/// tree.insert(2, 200);
/// tree.insert(1, 100);
/// assert_eq!(tree.search(&0), None);
/// assert_eq!(tree.search(&1), Some(&100));
/// assert_eq!(tree.search(&2), Some(&200));
/// ```
pub fn search(&self, key: &K) -> Option<&V> {
let mut root = &self.root;
while let Some(node) = root {
match key.cmp(&node.key) {
Ordering::Less => root = &node.left,
Ordering::Equal => return Some(&node.value),
Ordering::Greater => root = &node.right,
}
}
None
}
/// Inserts the given `value` into the tree with the given
/// `key`. Returns true if the node was inserted, or false
/// if the key was already in the tree.
///
/// # Example
///
/// ```
/// let mut tree = tree::Tree::<i32, i32>::new();
/// assert_eq!(tree.insert(1, 100), true);
/// assert_eq!(tree.insert(1, 200), false);
/// assert_eq!(tree.insert(2, 100), true);
/// ```
pub fn insert(&mut self, key: K, value: V) -> bool {
let mut root = &mut self.root;
loop {
let Some(node) = root else {
root.replace(Node::new(key, value));
return true;
};
match key.cmp(&node.key) {
Ordering::Less => root = &mut node.left,
Ordering::Equal => return false,
Ordering::Greater => root = &mut node.right,
}
}
}
/// Print the tree, if it is not empty.
pub fn print(&self) {
if let Some(node) = &self.root {
node.print();
}
}
}
// A node in the tree owns its key, value, and left and right
// children.
struct Node<K: Debug + Ord, V: Debug> {
key: K,
value: V,
left: Option<Box<Node<K, V>>>,
right: Option<Box<Node<K, V>>>,
}
impl<K: Debug + Ord, V: Debug> Node<K, V> {
/// Creates a new boxed node owning the given key and value.
pub fn new(key: K, value: V) -> Box<Node<K, V>> {
Box::new(Node {
key,
value,
left: None,
right: None,
})
}
/// Pre-order print of this node and its children.
pub fn print(&self) {
if let Some(left) = &self.left {
left.print();
}
println!("key = {:?}, value = {:?}", self.key, self.value);
if let Some(right) = &self.right {
right.print();
}
}
}
END `tree.rs`<---
Note that there's more code here than in your C code, but this
also does a lot more; this is generic over any key and value
types where the key type implements a total ordering, and where
both types implement the `Debug` trait (so they can be printed).
If I don't need any of that, does it get smaller? I don't think so.
Here's my attempt, and I expect that experienced Rust programmers will
find ways to improve on it. It includes a lookup function:
-------------------
use std::cmp::Ordering;
pub struct Tree<K: Ord, V> {
root: Subtree<K, V>,
}
type Subtree<K,V> = Option<Box<Node<K,V>>>;
struct Node<K: Ord, V> {
key: K,
val: V,
left: Subtree<K, V>,
right: Subtree<K, V>,
}
impl<K: Ord,V> Tree<K,V> {
pub fn new() -> Self {
Tree { root: None }
}
pub fn insert(&mut self, key: K, val: V) {
let mut t = &mut self.root;
while let Some(node)=t {
match key.cmp(&node.key) {
Ordering::Equal => {node.val = val; return;},
Ordering::Less => t = &mut node.left,
Ordering::Greater => t = &mut node.right,
};
};
*t = Some(Box::new(Node {key, val, left: None, right: None}))
}
pub fn lookup(&self, key: K) -> Option<&V> {
let mut t = &self.root;
while let Some(node)=t {
match key.cmp(&node.key) {
Ordering::Equal => return Some(&node.val),
Ordering::Less => t = &node.left,
Ordering::Greater => t = &node.right,
};
};
None
}
}
fn main() {
let mut q1 = Tree::new();
q1.insert(2,"abc".to_string());
q1.insert(3,"def".to_string());
q1.insert(1,"ghi".to_string());
q1.insert(3,"jkl".to_string());
println!("{:?}",q1.lookup(1));
println!("{:?}",q1.lookup(2));
println!("{:?}",q1.lookup(3));
}
----------------------------
Like your code and Java code, this has a separate type Tree with a
field root and with its own new function, while in C a "struct tree *" >variable can serve as root pointer; and this leads to an additional
line each in insert and lookup. Can we do without most of that
(havibf a constructor is useful) and just use Subtree instead of Tree?
insert now integrates search, because I could not satisfy the compiler
with a separate search; maybe a more experience Rust programmer can
separate it out again. The advantage is that there is no need to
combine the found and notfound cases in one return value in search,
only to handle them as separate cases in insert. But at least there
is no duplication of the comparison code.
The integration of search in insert led to a duplication of most of
the search code in lookup. However, requiring a mutable reference is
not necessary for the lookup code, so a search that takes a mutable
reference and returns a mutable reference is too restrictive for
lookup. But maybe a more experienced Rust programmer can write a
generic search that takes an &mut tree for insert, and a & for lookup.
Of course, if search is factored out, both clients have to explicitly
check for both outcomes and have code for both. But that's probably >preferable to duplicating the search.
though the pointer-to-pointer
to a node is not returned by `search`. If that is what you mean
by "technique" then you're right; doing that in Rust is
inconvenient because it violates the borrow checker's rules
about the existence of multiple simultaneous mutable references
to an object.
Yes, that's what I meant by "technique". And it's possible in safe
Rust, but I had a very hard time finding it (and for a while thought
that it is not possible). The fact that you thought it is not
possible in safe Rust means that it is not just my inexperience at
fault here.
But I claim that is just a detail, and not a
terribly interesting one at that; certainly not an impediment to >>substituting Rust for C in systems programming.
I think that, in order to be the rightful heir, safe Rust should be
able to do everything one can do in the other language that's not
inherently unsafe, and do it without needing to duplicate code of
other nastyness.
You may wonder about "inherently unsafe": I have no
definition for that yet, but I would not kno why inserting a node into
a search tree would ever fall into that category,
Even so, as I said, one _can_ use the same technique using a
smallish-bit of `unsafe` code.
That's cool, but given that I have difficulty understanding why the
compiler complained about my version with separate search, I feel very >uncomfortable in asserting to the compiler that my use of unsafe
features is safe once we leave the unsafe block.
Frankly, unless I was doing something very odd, I wouldn't write
my own binary search tree at all; the standard library's
collections implementations are pretty good
And I actually don't use binary search trees anyway, neither
self-written nor from libraries, because hash tables tend to be the
better alternative, except when I want to know the next-higher element
or somesuch. But being able to have a reference to a field is also
useful when implementing hash tables.
But anyway, the libraries have to be written in some language, and
given that Rust fans tell us to rewrite all C code in Rust, I would
expect that the library should be written in Rust, and, in particular
in safe Rust (otherwise, why rewrite it in Rust?).
And actually, we teach our students how to write this stuff (even in
Java), because we think it teaches them something useful. And at
least one Rust fan thinks so, too, and wrote "Learn Rust With Entirely
Too Many Linked Lists", where I found out how to implement the
mentioned technique in safe Rust.
Given that I do not understand the automagic stuff done by the Rust >>>compiler enough to get this kind of tree insertion past the borrow >>>checker, even though it is possible, I don't trust my capabilities to >>>audit unsafe Rust.
Is it significantly different than doing so in C, Pascal, or
Forth? See the second program, above; the use of `unsafe` in
the bits that correspond to your sketch in C is constrained to
two places that indirect raw pointers.
I may be able to shut up the compiler and get an executable with
"unsafe", but will the resulting program work? And I can have the
same assurance in C or Forth, so why should I switch to Rust to get
that?
It's so common that "fighting the borrow checker" is the
community's slogan to describe it.
Sounds to me like "the community" has found a convenient cliche to
avoid thinking about why people have difficulties with Rust, and why
these difficulties may increase as Rust advances.
From the books I read, I get the impression that
Rust used to have less automagic stuff, and much of the automagic was >>>added later, as a convenience for the programmers who already knew the >>>language. So the Rust programmers of the early days formed a mental >>>model based on "Pedestrian Rust" (i.e., with less automagic), and when >>>they see a program with more automagic, and need to understand some >>>conveniently short piece of Rust code in detail, they form a mental
model of the code in Pedestrian Rust. But how can a Rust programmer
who starts now learn Pedestrian Rust and how it relates to the modern >>>language. There are hints in the books I read, but not enough to get
a firm foundation.
Early Rust was very different, and much closer to a managed
language with garbage collection, green threads, a heavy-weight
runtime, etc. Put another way, the trend has been less and less >>"automagic" (as you put it) and making things more explicit over
time.
What I mean is the automatic dropping, and the fact that the Box stuff
only occurs in the type declarations, but is automatically
dereferenced or otherwise automatically handled; "let x = ..."
produces a type automatically (but I guess you could put a type
declaration there, though).
You keep conflating things you are not familiar with in a
language you are not familiar with, with the idea that those
things are "automagic." But really there is no magic here, and
I suspect the answer to your question about how one learns to
adapt their mental model of Rust is to first stop thinking of it
as magical.
The language hides a number of things with automation, and given that
I have no good mental model for that, following Clarke's third law
("Any sufficiently advanced technology is indistinguishable from
magic."), I refer to that automatic stuff as "automagic stuff". I
don't think it's magic, it just tries to be indistinguishable. It
shouldn't.
[email protected] (Dan Cross) writes:
In article <[email protected]>,
Anton Ertl <[email protected]> wrote:
Tim Rentsch <[email protected]> writes:
That's good to know but what is a good resource to learn the Rust >>>language? I've looked at the Rust book several times and each
time come away rather frustrated.
If you mean "The Rust Programming Language", I came away from it >>disillusioned.
The reason was that I could not get a search-tree insertion
technique to work that can be implemented in e.g., Pascal, C, and
Forth, but not in Java (because in Java I cannot produce references
to a field inside an object).
I think what you mean to say is that you couldn't find a way to
implement that in the manner you were accustomed to thinking
about the problem, given your prior experience.
My goal was to implement search tree insertion as efficiently and
compactly as in Pascal,
C or Forth (and in contrast to Java), ideally
in safe Rust. Here it is in C (untested):
struct tree {
int key; /* or some other key type */
int val; /* or some other value type */
struct tree *left;
struct tree *right;
};
struct tree **search(struct tree **rootp, int key)
{
while ((root=*rootp)!=NULL && key != root->key) {
if (key < root->key)
rootp = &(root->left);
else
rootp = &(root->right);
}
return rootp;
}
void insert(struct tree **rootp, int key, int val)
{
struct tree **nodep = **search(rootp, key);
struct tree *node = *nodep;
if (node == NULL) {
node = malloc(sizeof(tree));
node->key = key;
node->left = NULL;
node->right = NULL;
*nodep = node;
}
node->val = val;
}
Passing a reference to a mutable field is what is done in these
languages, and I don't know an efficient and similarly compact
technique that does not pass a reference to a mutable field.
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable array.
Even if it isn't, the fact that I start by verifying that the slice
extends at least 7 bytes past the p index means that all of p,
p+1,p+2...p+6 must be valid, right?
On Tue, 06 Oct 2026 08:29:19 GMT
[email protected] (Anton Ertl) wrote:
My goal was to implement search tree insertion as efficiently and
compactly as in Pascal,
I suppose, you mean some extended Pascal dialect here, because I can not >imagine how you code an example below in Standard Pascal.
Then again, I learned Pascal ~40 years ago and didn't use it in
practice since 1989 or 1990, so can easily miss something.
In article <[email protected]>,[Anton Ertl:]
Anton Ertl <[email protected]> wrote: >>[email protected] (Dan Cross) writes:
I do not know what you are referring to when you write, the
"fact that you did not find it"; did you not notice that I gave
you _two_ programs, one of which was pretty much the same as the
C program you gave? What, exactly, are you referring to?
Like your code and Java code, this has a separate type Tree with a
field root and with its own new function, while in C a "struct tree *" >>variable can serve as root pointer; and this leads to an additional
line each in insert and lookup. Can we do without most of that
(havibf a constructor is useful) and just use Subtree instead of Tree?
Sure. I do not think it is an interesting exercise, though.
The integration of search in insert led to a duplication of most of
the search code in lookup. However, requiring a mutable reference is
not necessary for the lookup code, so a search that takes a mutable >>reference and returns a mutable reference is too restrictive for
lookup. But maybe a more experienced Rust programmer can write a
generic search that takes an &mut tree for insert, and a & for lookup.
If it offends you that much, you could use a macro.
By analogy, in your C, how would it affect things if you wanted
to `const` qualify the tree pointer for lookups?
I think that, in order to be the rightful heir, safe Rust should be
able to do everything one can do in the other language that's not >>inherently unsafe, and do it without needing to duplicate code of
other nastyness.
I think that criteria is both silly and arbitrary, and has no
basis in actual practice. You are asserting that an arbitrary
implementation detail of this one program is a useful yardstick
by which to measure a language's applicability to an entire
problem domain,
You may wonder about "inherently unsafe": I have no
definition for that yet, but I would not kno why inserting a node into
a search tree would ever fall into that category,
Actually, I think perhaps it's the other way around: you do not
seem to know what "unsafe" means in a Rust context. It is
merely a statement that the compiler cannot infer that the
"unsafe" code meets the language's memory safety criteria, and
that the programmer is asserting that it does;
that is exactly
the same as every line in a C program.
I'm afraid that sounds like a problem for you to deal with, not
a problem with Rust.
What I mean is the automatic dropping, and the fact that the Box stuff
only occurs in the type declarations, but is automatically
dereferenced or otherwise automatically handled; "let x = ..."
produces a type automatically (but I guess you could put a type
declaration there, though).
Drop and box as a smart pointer is simply "RAII", which has been
in C++ since the 1980s. Hindley and Milner created the type
inference rules you are referring to here in the 1970s.
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable array.
Even if it isn't, the fact that I start by verifying that the slice----------
extends at least 7 bytes past the p index means that all of p,
p+1,p+2...p+6 must be valid, right?
Terje
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable
array.
Can I get a definition of what mutable in Rust means ??
Michael S <[email protected]> writes:
On Tue, 06 Oct 2026 08:29:19 GMT
[email protected] (Anton Ertl) wrote:
My goal was to implement search tree insertion as efficiently and
compactly as in Pascal,
I suppose, you mean some extended Pascal dialect here, because I can
not imagine how you code an example below in Standard Pascal.
Then again, I learned Pascal ~40 years ago and didn't use it in
practice since 1989 or 1990, so can easily miss something.
Page 75 and 76 of my copy of "Programming in Modula-2" (Second
Edition) contain this:
TreePtr = POINTER TO TreeNode;
TreeNode =
Record
key: CARDINAL;
data: ...
left, right: TreePtr;
END;
PROCEDURE search(VAR p: TreePtr; x: CARDINAL): TreePtr;
BEGIN
IF p # NIL THEN
IF p^.key < x THEN
RETURN search(p^.right,x)
ELSIF p^.key > x THEN
RETURN search(p^.left, x)
ELSE
RETURN p
END
ELSE (* not found, hence insert *)
NEW(p);
WITH p^ DO
key := x;
left := NIL;
right := NIL;
END;
RETURN p
END
END search
You would invoke this with
search(root,needle)^.data := ...;
in order to actually change the field "data", performing the insert functionality.
One would not want to use this procedure for lookup, given that it
inserts a node with an uninitialized data field when the key is not
found. Wirth gives a loop implementation of lookup before the
PROCEDURE search.
Note that the "VAR" in front of the p makes p a reference parameter
rather than a value parameter (like x), which is key to making this
work for insertion. This language feature is also present in Pascal;
the Pascal syntax is different, but I don't think that prevents
implementing this approach.
Using recursion is necessary here because Modula-2 and Pascal support references to variables or fields only for parameters. But the
recursive calls are tail-recursive (I don't think that Wirth's
Modula-2 or Parcal compilers optimized tail recursion, though).
- anton
MitchAlsup wrote:
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable
array.
Can I get a definition of what mutable in Rust means ??
It does mean that the function which receives a non-mut parameter, or
itself declares som non-mut local variables, cannot modify any of them.
So yes, very similar to "not const".
You can discuss if its a good idea to make non-mutability the default instead of the opposite. Personally I like that part.
Which leads to the question? Can an experienced Rust programmer do a
search that works like the C search(), and an insert that calls
search() and then uses the reference to either update a node or insert
a new one?
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
[on learning Rust]
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
(Please excuse my pursuing this non-arch topic...)
Question for any Rusters in the group --
What is the best was to learn the Rust programming language?
Which books or other documents are the best introductory or
reference materials?
Disclaimer: My experience with Rust is more than zero but
not a lot more than zero.
Without knowing what methods you have tried, what books you have
already explored, what you found dissatisfying about them, and
why, it is impossible to give you a good answer.
But if you receive a "pointer to const" parameter to a function, you can still write via a cast version of the pointer if the original object was
not defined as "const". (So objects defined as "const" may be placed in read-only memory of some kind, and trying to change them might cause a crash, signal, bus-error, etc.)
[email protected] (Dan Cross) writes:
In article <[email protected]>,
Tim Rentsch <[email protected]> wrote:
Terje Mathisen <[email protected]> writes:
[on learning Rust]
Quite often, when "fighting the borrow checker", typically because
I'm updating a complicated data structure in two locations at the
same time, I'll fall back to "write Fortran in Rust", with every
access indexed.
This is BTW one of those problems the brand new upcoming borrow
checker is supposed to be far better at figuring out: If I'm
updating totally separate parts of the same larger structure,
then it should be OK to mutate both at the same time.
I do realize that there are far more Rusty ways to solve this
type of issue, they just don't come naturally to me yet.
(Please excuse my pursuing this non-arch topic...)
Question for any Rusters in the group --
What is the best was to learn the Rust programming language?
Which books or other documents are the best introductory or
reference materials?
Disclaimer: My experience with Rust is more than zero but
not a lot more than zero.
Without knowing what methods you have tried, what books you have
already explored, what you found dissatisfying about them, and
why, it is impossible to give you a good answer.
Fortunately some other people found it possible to give
helpful replies.
David Brown <[email protected]> schrieb:
But if you receive a "pointer to const" parameter to a function, you can
still write via a cast version of the pointer if the original object was
not defined as "const". (So objects defined as "const" may be placed in
read-only memory of some kind, and trying to change them might cause a
crash, signal, bus-error, etc.)
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
and get a printout of 42 (21 is only half the truth,
remember!) because the compiler put the constants in read/write
storage and arguments were passed via address. (We used to call
this "Variation der Konstanten", which translates to "variation
of parameters", from ODE theory.
On modern systems, you get a bus error:
$ ./a.out
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
#0 0x72b109a23e59 in ???
#1 0x72b109a22e75 in ???
#2 0x72b10964532f in ???
at ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
#3 0x401172 in foo_
at /tmp/crash.f90:6
#4 0x40118f in MAIN__
at /tmp/crash.f90:1
#5 0x401226 in main
at /tmp/crash.f90:3
But it would be interesting to be able to put arguments into
storage which can be changed by the caller, but not the callee,
sort of a read-only stack, but I am not sure how one would
efficiently implement that.
(Modern Fortran offers INTENT for that, so
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
INTEGER, INTENT(IN) :: N
N = 42
END
gets you
error.f90:7:7:
7 | N = 42
| 1~
Error: Dummy argument 'n' with INTENT(IN) in variable definition context (assignment) at (1)
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable
array.
Can I get a definition of what mutable in Rust means ??
Intuition would imply the inverse of opposite of const from C.
[snip]
I think (and you will hopefully correct me if I am wrong!) in Rust if a >function receives a non-mutable reference as a parameter, it is not
allowed to cast it to a mutable reference.
You can discuss if its a good idea to make non-mutability the default
instead of the opposite. Personally I like that part.
It certainly sounds good to me.
David Brown <[email protected]> schrieb:
But if you receive a "pointer to const" parameter to a function, you can
still write via a cast version of the pointer if the original object was
not defined as "const". (So objects defined as "const" may be placed in
read-only memory of some kind, and trying to change them might cause a
crash, signal, bus-error, etc.)
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
and get a printout of 42 (21 is only half the truth,
remember!) because the compiler put the constants in read/write
storage and arguments were passed via address.
Forth is arguably even better for this :
: 21 42 ;
21 .
42 ok
That is - you can redefine the word "21" to put the value 42 on the
stack. Then if you ask for the number "21", you get the value 42. It
is not an oddity of the implementation, but the way the language works.
In article <[email protected]>,
MitchAlsup <[email protected]d> wrote:
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable
array.
Can I get a definition of what mutable in Rust means ??
So this will fail to compile:
? let mut foo = something();
? let foomut = &mut foo; // Ok: only mutable
? let barmut = &mut foo // Fail: a mut ref to foo exists
By default, all assignments in Rust have _move_ semantics: after
the assignment, the source is considered destroyed, and any
references to it are no longer valid. That's why one can assign
to `foo` and on line 5 and then take a mut reference to it on
line 7; the assignment on 5 implicitly dropped the original
`foo` object created on line 1 and replaced it with a new
object, so the mut reference it created on line 3 is invalid;
an attempt to use it subsequently would fail to compile.
One can define a `copy` operator for types that should not have
move semantics; this is usually applied to trivial types (like
integers).
One may ask, "why bother with all of this stuff?" and the answer
is that Rust is designed for a multicore world. There is one
other wrinkle here that gives Rust a lot of power, and that's
how all of this stuff interacts with data races: safe Rust code
is data race free.
Rust does not mandate a particular shape or type of (software)
thread, but because of the "mut ref ^ immut ref" rule, enforced
by the borrow checker at compile-time, safe Rust programs cannot
race on access to data, even in the face of multithreading.
Rust handles this by exposing two "marker traits", `Sync` and
`Send`. To quote the _Rustinomicon_, "A type is `Send` if it is
safe to send it to another thread" and "A type is `Sync` if it
is safe to share between threads (T is `Sync` if and only if
`&T` is `Send`)." That is, a type can be _shared_ between
threads if it is safe to send an immutable reference to it from
one thread to another, and the compiler can check borrows
against the lifetimes of objects, hence, can detect data races
_statically, at compile time_. (https://doc.rust-lang.org/nomicon/send-and-sync.html)
Those of us familiar with writing multithreaded programs will
recognize the power of these constructs: these rules, taken
together, statically eliminate entire categories of common
memory and concurrency problems.
Anyway, that what "mutable" means, in a nutshell, and why it is
important.
- Dan C.
On Mon, 12 Oct 2026 11:25:32 -0000 (UTC), Thomas Koenig <[email protected]> wrote:
David Brown <[email protected]> schrieb:
But if you receive a "pointer to const" parameter to a function, you can >> still write via a cast version of the pointer if the original object was >> not defined as "const". (So objects defined as "const" may be placed in >> read-only memory of some kind, and trying to change them might cause a
crash, signal, bus-error, etc.)
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
and get a printout of 42 (21 is only half the truth,
remember!) because the compiler put the constants in read/write
storage and arguments were passed via address.
And the proper way to correct this error is obvious. Not by putting
constants in a segment marked read-only. But in properly implementing language semantics.
So the values of constants are stored in memory.
However, when a constant is passed to a function as an argument,
clearly that constant should be treated as an *expression*, not as a variable. So you copy it to a temporary, and pass the address of the temporary to the function.
Not doing that was the bug.
John Savard--- Synchronet 3.22a-Linux NewsLink 1.2
David Brown <[email protected]> writes:
Forth is arguably even better for this :
: 21 42 ;
21 .
42 ok
That is - you can redefine the word "21" to put the value 42 on the
stack. Then if you ask for the number "21", you get the value 42. It
is not an oddity of the implementation, but the way the language works.
Yes, but used like this, it's only a curiosity; nobody does this
except to show that it can be done.
However, there are cases where this is practically relevant:
hex \ change to base #16
add \ is this a dictionary word, or a number?
The classic ways to avoid this has been to
1) prefix hex numbers with 0 if they would otherwise start with a
letter. Dictionary words rarely start with digits, and when they
do, they don't conflict with hex numbers.
2) Use "," at the end of assembly language mnemonics, e.g. "add,":
Benefits: 1) Avoids conflicts with hex numbers. 2) For postfix
assemblers (e.g. "r0 r1 r2 add,", the "," nicely separates the
instruction from the next. But a significant number of Forth
assemblers do not follow this convention.
In modern Forth another way is to avoid changing BASE, and just prefix
hex numbers with "$", e.g., "$add".
When you do, e.g.,
hex
: add ;
in Gforth, Gforth tells you: "warning: defined literal add as word".
It does not tell you that when you define "add" when the base is
decimal, however.
- anton
In article <[email protected]>,[Anton Ertl:]
Anton Ertl <[email protected]> wrote: >>>[email protected] (Dan Cross) writes:
I do not know what you are referring to when you write, the
"fact that you did not find it"; did you not notice that I gave
you _two_ programs, one of which was pretty much the same as the
C program you gave? What, exactly, are you referring to?
I was referring to your safe Rust solution. However, I did not look
at it closely enough, especially at your insert, to realize that what
you had done is to turn search into lookup, and that you do not call
search from insert, like the C version does.
I just saw that there is a similar match in your insert as in search
(and the non-recursive Java version would also have that) and set out
to refine an existing linked-list implementation with insertion at the
end into a tree insertion that does not repeat the match, and I used
your match code from search for the parts that the linked-list
implementation does not do.
The list variant has the walk to the end separated from the actual
insertion, and the C tree variant also does it that way, and I wanted
the Rust version to do the same, but could not get it past the Rust
compiler. The insertion code did not look particularly nice, either, >checking for something that is alreadu known in the search code. so
to work around the compiler problem, I eventually decided to inline
the search code into insert, and I could get that to compile.
For testing I then added a lookup, which was derived from the search
part of insert, without mut stuff, so it's full circle back at your
search.
Anyway, yes, your insert does not work the Java version, but like the
C version with search() inlined into insert(), and like the insert I
finally arrived at.
Which leads to the question? Can an experienced Rust programmer do a
search that works like the C search(), and an insert that calls
search() and then uses the reference to either update a node or insert
a new one?
Like your code and Java code, this has a separate type Tree with a
field root and with its own new function, while in C a "struct tree *" >>>variable can serve as root pointer; and this leads to an additional
line each in insert and lookup. Can we do without most of that
(havibf a constructor is useful) and just use Subtree instead of Tree?
Sure. I do not think it is an interesting exercise, though.
I tried it for a while, and did not get anything but more and more
compiler errors.
The integration of search in insert led to a duplication of most of
the search code in lookup. However, requiring a mutable reference is
not necessary for the lookup code, so a search that takes a mutable >>>reference and returns a mutable reference is too restrictive for
lookup. But maybe a more experienced Rust programmer can write a
generic search that takes an &mut tree for insert, and a & for lookup.
If it offends you that much, you could use a macro.
Good to know that there is this option.
By analogy, in your C, how would it affect things if you wanted
to `const` qualify the tree pointer for lookups?
C works perfectly fine without "const", so why would I want to do that?
By contrast, Rust is picky about "mut". If I add it everywhere where
the syntax allows it (which might be the analogon of leaving "const"
away), the borrow checker will complain about programs that it
accepted before adding all the "mut"s.
I think that, in order to be the rightful heir, safe Rust should be
able to do everything one can do in the other language that's not >>>inherently unsafe, and do it without needing to duplicate code of
other nastyness.
I think that criteria is both silly and arbitrary, and has no
basis in actual practice. You are asserting that an arbitrary >>implementation detail of this one program is a useful yardstick
by which to measure a language's applicability to an entire
problem domain,
Not one detail, the collection of all the details. This is one.
You may wonder about "inherently unsafe": I have no
definition for that yet, but I would not kno why inserting a node into
a search tree would ever fall into that category,
Actually, I think perhaps it's the other way around: you do not
seem to know what "unsafe" means in a Rust context. It is
merely a statement that the compiler cannot infer that the
"unsafe" code meets the language's memory safety criteria, and
that the programmer is asserting that it does;
I understand that.
that is exactly
the same as every line in a C program.
The decisive difference is that in a C program there is no end to an
unsafe block, where I have to satisfy safety criteria that are beyond
my experience level.
I'm afraid that sounds like a problem for you to deal with, not
a problem with Rust.
Sure, and the way I deal with that is to not use unsafe Rust.
What I mean is the automatic dropping, and the fact that the Box stuff >>>only occurs in the type declarations, but is automatically
dereferenced or otherwise automatically handled; "let x = ..."
produces a type automatically (but I guess you could put a type >>>declaration there, though).
Drop and box as a smart pointer is simply "RAII", which has been
in C++ since the 1980s. Hindley and Milner created the type
inference rules you are referring to here in the 1970s.
And?
Which leads to the question? Can an experienced Rust programmer do a
search that works like the C search(), and an insert that calls
search() and then uses the reference to either update a node or insert
a new one?
IIUC it can but at the cost of making `search` take a `&mut` reference.
I get the impression that you'd otherwise need "mutability
polymorphism", i.e. a single piece of code that can be instantiated
either for `&` or for `&mut`. You can probably avoid writing the code
twice, tho, by abusing macros (aka "template polymorphism").
However, when a constant is passed to a function as an argument,
clearly that constant should be treated as an *expression*, not as a
variable. So you copy it to a temporary, and pass the address of the
temporary to the function.
And you think the language designers in 1956 could have figured that out ???
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
And you think the language designers in 1956 could have figured that out ???
[email protected] (Dan Cross) posted:
First off: thank you dan for a well done explanation.
In article <[email protected]>,--------snip of what I understand clearly------------
MitchAlsup <[email protected]d> wrote:
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable >> >> array.
Can I get a definition of what mutable in Rust means ??
So this will fail to compile:
? let mut foo = something();
? let foomut = &mut foo; // Ok: only mutable
? let barmut = &mut foo // Fail: a mut ref to foo exists
Is this supposed to work across threads ?? so, only 1 thread has write
access at any given point in time.
If so, how does one thread hand-off mutability to another ??
--------------snip again--------
By default, all assignments in Rust have _move_ semantics: after
the assignment, the source is considered destroyed, and any
references to it are no longer valid. That's why one can assign
to `foo` and on line 5 and then take a mut reference to it on
line 7; the assignment on 5 implicitly dropped the original
`foo` object created on line 1 and replaced it with a new
object, so the mut reference it created on line 3 is invalid;
an attempt to use it subsequently would fail to compile.
This is clever.
One can define a `copy` operator for types that should not have
move semantics; this is usually applied to trivial types (like
integers).
One may ask, "why bother with all of this stuff?" and the answer
is that Rust is designed for a multicore world. There is one
other wrinkle here that gives Rust a lot of power, and that's
how all of this stuff interacts with data races: safe Rust code
is data race free.
See question above.
Rust does not mandate a particular shape or type of (software)
thread, but because of the "mut ref ^ immut ref" rule, enforced
by the borrow checker at compile-time, safe Rust programs cannot
race on access to data, even in the face of multithreading.
Rust handles this by exposing two "marker traits", `Sync` and
`Send`. To quote the _Rustinomicon_, "A type is `Send` if it is
safe to send it to another thread" and "A type is `Sync` if it
is safe to share between threads (T is `Sync` if and only if
`&T` is `Send`)." That is, a type can be _shared_ between
threads if it is safe to send an immutable reference to it from
one thread to another, and the compiler can check borrows
against the lifetimes of objects, hence, can detect data races
_statically, at compile time_.
(https://doc.rust-lang.org/nomicon/send-and-sync.html)
Those of us familiar with writing multithreaded programs will
recognize the power of these constructs: these rules, taken
together, statically eliminate entire categories of common
memory and concurrency problems.
Anyway, that what "mutable" means, in a nutshell, and why it is
important.
Thanks again for a complete explanation.
[email protected] (John Savard) posted:
On Mon, 12 Oct 2026 11:25:32 -0000 (UTC), Thomas Koenig
<[email protected]> wrote:
David Brown <[email protected]> schrieb:
But if you receive a "pointer to const" parameter to a function, you can >> >> still write via a cast version of the pointer if the original object was >> >> not defined as "const". (So objects defined as "const" may be placed in >> >> read-only memory of some kind, and trying to change them might cause a >> >> crash, signal, bus-error, etc.)
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
and get a printout of 42 (21 is only half the truth,
remember!) because the compiler put the constants in read/write
storage and arguments were passed via address.
And the proper way to correct this error is obvious. Not by putting
constants in a segment marked read-only. But in properly implementing
language semantics.
So the values of constants are stored in memory.
However, when a constant is passed to a function as an argument,
clearly that constant should be treated as an *expression*, not as a
variable. So you copy it to a temporary, and pass the address of the
temporary to the function.
And you think the language designers in 1956 could have figured that out ???
Not doing that was the bug.
Some would disagree.
You can probably avoid writing the code twice, tho, by abusing
macros (aka "template polymorphism").
In article <[email protected]>,
MitchAlsup <[email protected]d> wrote:
[email protected] (Dan Cross) posted:
First off: thank you dan for a well done explanation.
In article <[email protected]>,--------snip of what I understand clearly------------
MitchAlsup <[email protected]d> wrote:
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable >> >> array.
Can I get a definition of what mutable in Rust means ??
So this will fail to compile:
? let mut foo = something();
? let foomut = &mut foo; // Ok: only mutable
? let barmut = &mut foo // Fail: a mut ref to foo exists
Is this supposed to work across threads ?? so, only 1 thread has write >access at any given point in time.
Yes, that's right.
If so, how does one thread hand-off mutability to another ??
Typically, you use a mutual exclusion primitive of one kind or
another to serialize access to mutable state. For example, the
standard library includes the usual complement of Mutex,
reader/writer locks, condition variables, barriers, reference
counters, thread-safe channels, and so on. For bare-metal
programs we have to implement these ourselves, but there are
some third-party implementations that can help (for example, the
`spin` crate implements spinlocks, providing, an interface that
looks close to `std::sync::Mutex`).
Something I should have mentioned earlier is that Rust object
has a single, well-defined owner, and that references to that
object are borrowed _from the owner_.
So, using `Mutex` as an example, we make the `Mutex` itself the
owner of the object: the `.lock()` method lets threads borrow
mutable references to the object, and the serialization inherent
in the Mutex type and the locking protocol enforces the
"mut ^ immut" semantics across threads.
Similarly, a `RwLock` can hand out multiple immutable refs to
the object, but keeps track of them, and only lets one borrower
access a `mut ref` at a time, and only when there are no
outstanding immutable refs to it.
In fact, it's slightly more complex in that `mutex.lock()`
actually returns a proxy object that holds onto the reference;
its return type is actually, `LockResult<LockGuard<'_, T>>`,
where `'_` is an anonymous lifetime, and `T` is the type of the
thing protected by the `Mutex`. `LockResult` lets someone
detect a lock that has been poisoned; the `LockGuard` is the
thing that holds the ref. When it goes otu of scope, it
automatically unlocks the mutex, and it implements a trait
called `Deref` to forward uses on to the ref to the locked
object.
This all intersects with the `Send`/`Sync` stuff; `Mutex` is
`Sync`, and `Mutex` objects themselves are treated as immutable.
So to share access to a `Foo` between threads, I use something
like a `Mutex<Foo>`. This is an example of "Interior
Mutability", where something immutable (the `Mutex` object
itself) can hold onto something that is mutable.
This raises a question: if all this machinery is required for
safe access to shared data across threads anyway, then why all
the language-level rules? The answer is hinted at by the mutex
object itself is immutable, we can hand out references to it
easily. Since it owns the (mutable) object the lock protects,
it can decide how to hand out references to the object.
--------------snip again--------
By default, all assignments in Rust have _move_ semantics: after
the assignment, the source is considered destroyed, and any
references to it are no longer valid. That's why one can assign
to `foo` and on line 5 and then take a mut reference to it on
line 7; the assignment on 5 implicitly dropped the original
`foo` object created on line 1 and replaced it with a new
object, so the mut reference it created on line 3 is invalid;
an attempt to use it subsequently would fail to compile.
This is clever.
One can define a `copy` operator for types that should not have
move semantics; this is usually applied to trivial types (like
integers).
One may ask, "why bother with all of this stuff?" and the answer
is that Rust is designed for a multicore world. There is one
other wrinkle here that gives Rust a lot of power, and that's
how all of this stuff interacts with data races: safe Rust code
is data race free.
See question above.
See above; hopefully that helps?
There's one other thing here that may be interesting: how does
one implement any of this stuff _in Rust_? In particular, how
does one implement interior mutability?
The answer is using something called the `UnsafeCell`: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
`UnsafeCell` allows mutation of an object (the thing owned by
the cell) through a shared (what up to now I've been calling an
"immutable") reference to the cell. It's the one place that
permits that behavior.
The implementatin of a spinlock in the `spin` crate is an
interesting read to understand the mechanics here: https://codeberg.org/zesterer/spin/src/branch/master/src/mutex/spin.rs
Rust does not mandate a particular shape or type of (software)
thread, but because of the "mut ref ^ immut ref" rule, enforced
by the borrow checker at compile-time, safe Rust programs cannot
race on access to data, even in the face of multithreading.
Rust handles this by exposing two "marker traits", `Sync` and
`Send`. To quote the _Rustinomicon_, "A type is `Send` if it is
safe to send it to another thread" and "A type is `Sync` if it
is safe to share between threads (T is `Sync` if and only if
`&T` is `Send`)." That is, a type can be _shared_ between
threads if it is safe to send an immutable reference to it from
one thread to another, and the compiler can check borrows
against the lifetimes of objects, hence, can detect data races
_statically, at compile time_.
(https://doc.rust-lang.org/nomicon/send-and-sync.html)
Those of us familiar with writing multithreaded programs will
recognize the power of these constructs: these rules, taken
together, statically eliminate entire categories of common
memory and concurrency problems.
Anyway, that what "mutable" means, in a nutshell, and why it is
important.
Thanks again for a complete explanation.
Happy to do it.
- Dan C.
In article <[email protected]>,
MitchAlsup <[email protected]d> wrote:
[email protected] (John Savard) posted:
On Mon, 12 Oct 2026 11:25:32 -0000 (UTC), Thomas Koenig
<[email protected]> wrote:
David Brown <[email protected]> schrieb:
But if you receive a "pointer to const" parameter to a function, you can
still write via a cast version of the pointer if the original object was
not defined as "const". (So objects defined as "const" may be placed in
read-only memory of some kind, and trying to change them might cause a >> >> crash, signal, bus-error, etc.)
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
and get a printout of 42 (21 is only half the truth,
remember!) because the compiler put the constants in read/write
storage and arguments were passed via address.
And the proper way to correct this error is obvious. Not by putting
constants in a segment marked read-only. But in properly implementing
language semantics.
So the values of constants are stored in memory.
However, when a constant is passed to a function as an argument,
clearly that constant should be treated as an *expression*, not as a
variable. So you copy it to a temporary, and pass the address of the
temporary to the function.
And you think the language designers in 1956 could have figured that out ???
I don't think that's the right framing.
Not doing that was the bug.
Some would disagree.
That it's a bug? Clearly, changing the value of some constant
is surprising, at best.
- Dan C.
However, when a constant is passed to a function as an argument,
clearly that constant should be treated as an *expression*, not as a
variable. So you copy it to a temporary, and pass the address of the
temporary to the function.
And you think the language designers in 1956 could have figured that out ???
I don't think that's the right framing.
Not doing that was the bug.
Some would disagree.
That it's a bug? Clearly, changing the value of some constant
is surprising, at best.
David Brown <[email protected]> writes:
Forth is arguably even better for this :
: 21 42 ;
21 .
42 ok
That is - you can redefine the word "21" to put the value 42 on the
stack. Then if you ask for the number "21", you get the value 42. It
is not an oddity of the implementation, but the way the language works.
Yes, but used like this, it's only a curiosity; nobody does this
except to show that it can be done.
Forth originated in astronomy, if I remember correctly. I do remember
that in an astronomy-domain Forth program, where it was often necessary
to transform the celestial coordinates of stars from the standard
catalog epoch (the start of the year 1950) to the current time, the word >1950.0 was defined to perform that transformation.
That Forth system did not have floating-point support, so 1950.0 could
not be understood as a floating-point literal. But if the program had
been ported to a Forth system with floating-point support, confusion
could have resulted.
hex \ change to base #16
add \ is this a dictionary word, or a number?
The classic ways to avoid this has been to
1) prefix hex numbers with 0 if they would otherwise start with a
letter. Dictionary words rarely start with digits, and when they
do, they don't conflict with hex numbers.
[email protected] (Anton Ertl) writes:
hex \ change to base #16
add \ is this a dictionary word, or a number?
The classic ways to avoid this has been to
1) prefix hex numbers with 0 if they would otherwise start with a
letter. Dictionary words rarely start with digits, and when they
do, they don't conflict with hex numbers.
Alternately, Forth could adopt practices which have become broad,
de facto standards. So, referencing above, instead hex numbers
have a 0xadd prefix, and you don't switch the base of the system.
While you're at it, start the transition to "//" as the comment
token.
From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
Niklas Holsti <[email protected]d> writes:
Forth originated in astronomy, if I remember correctly. I do remember
that in an astronomy-domain Forth program, where it was often necessary
to transform the celestial coordinates of stars from the standard
catalog epoch (the start of the year 1950) to the current time, the word
1950.0 was defined to perform that transformation.
That is unlikely, as explained below.
That Forth system did not have floating-point support, so 1950.0 could
not be understood as a floating-point literal. But if the program had
been ported to a Forth system with floating-point support, confusion
could have resulted.
Most Forth systems recognize strings like 1950.0 as double-cell
numbers. That's expecially true in early systems, where 16-bit cells
were too small for the arithmetic needs of many applications
(including in astronomy).
In early systems fixed-point was used a lot, and 1950.0 isThe Forth program in question represented angles as fixed-point
interpreted as 19500 with a scale factor of 1/10.
[email protected] (Anton Ertl) writes:
hex \ change to base #16
add \ is this a dictionary word, or a number?
The classic ways to avoid this has been to
1) prefix hex numbers with 0 if they would otherwise start with a
letter. Dictionary words rarely start with digits, and when they
do, they don't conflict with hex numbers.
Alternately, Forth could adopt practices which have become broad,
de facto standards. So, referencing above, instead hex numbers
have a 0xadd prefix, and you don't switch the base of the system.
While you're at it, start the transition to "//" as the comment
token. From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
On 2026-10-13 19:09, Andy Valencia wrote:
[email protected] (Anton Ertl) writes:
hex \ change to base #16
add \ is this a dictionary word, or a number?
The classic ways to avoid this has been to
1) prefix hex numbers with 0 if they would otherwise start with a
letter. Dictionary words rarely start with digits, and when they >>> do, they don't conflict with hex numbers.
Alternately, Forth could adopt practices which have become broad,
de facto standards. So, referencing above, instead hex numbers
have a 0xadd prefix, and you don't switch the base of the system.
While you're at it, start the transition to "//" as the comment
token. From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
Ada uses "--".
Andy Valencia <[email protected]> writes:
From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
Most programming languages use something else for comments: Unix
shells, Lisp, Makefiles, assembly language, Postscript and Ada come to
my mind. Maybe some of them also accept //, but I have yet to see a
program in one of these languages that uses such a comment.
On Mon, 12 Oct 2026 11:25:32 -0000 (UTC), Thomas Koenig
<[email protected]> wrote:
David Brown <[email protected]> schrieb:
But if you receive a "pointer to const" parameter to a function, you can >>> still write via a cast version of the pointer if the original object was >>> not defined as "const". (So objects defined as "const" may be placed in >>> read-only memory of some kind, and trying to change them might cause a
crash, signal, bus-error, etc.)
In FORTRAN, it used to be possible on systems such as /360 to write
CALL FOO(21)
WRITE (*,*) 21
END
SUBROUTINE FOO(N)
N = 42
END
and get a printout of 42 (21 is only half the truth,
remember!) because the compiler put the constants in read/write
storage and arguments were passed via address.
And the proper way to correct this error is obvious. Not by putting
constants in a segment marked read-only. But in properly implementing language semantics.
So the values of constants are stored in memory.
However, when a constant is passed to a function as an argument,
clearly that constant should be treated as an *expression*, not as a variable. So you copy it to a temporary, and pass the address of the temporary to the function. Not doing that was the bug.
While you're at it, start the transition to "//" as the comment
token. From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
While you're at it, start the transition to "//" as the comment
token. From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
There are many many more, including a little known language named after something like a snake or some British comedy. In that language `//` is
the integer division operation.
On 2026-10-13 13:27, Anton Ertl wrote:
Niklas Holsti <[email protected]d> writes:
Forth originated in astronomy, if I remember correctly. I do remember
that in an astronomy-domain Forth program, where it was often necessary
to transform the celestial coordinates of stars from the standard
catalog epoch (the start of the year 1950) to the current time, the word >>> 1950.0 was defined to perform that transformation.
That is unlikely, as explained below.
I am rather sure that my memory is correct.
In early systems fixed-point was used a lot, and 1950.0 isThe Forth program in question represented angles as fixed-point
interpreted as 19500 with a scale factor of 1/10.
fractions (0 <= x < 1) of a full circle. I don't remember the number of
bits used.
Even if that Forth system by default interpreted 1950.0 as a double-cell >number, apparently it was still possible to override that interpretation
by defining a word called 1950.0.
What would have been more in line with...
practice is to define a word 1950.0 which does what writing 1950.0
without such a word does (if the BASE is right): to push a double-cell
number 19500.
EPOCH still might have been a better name, though.
Andy Valencia wrote:
While you're at it, start the transition to "//" as the comment
token. From memory, who's holding out? Only Forth, Smalltalk, and
COBOL come to mind.
There are many many more, ...
Seriously, no thoughts on block comments? Shouldn't every language be
forced to use /* */
On Wed, 14 Oct 2026 12:14:30 -0400, George Neuner
<[email protected]> wrote:
Seriously, no thoughts on block comments? Shouldn't every language be >>forced to use /* */
Using pairs of special characters for *any* purpose, from ** for exponentiation in FORTRAN onwards, and including <= and >= as
relational operators, vastly complicates the syntax decoding of
languages.
On Wed, 14 Oct 2026 12:14:30 -0400, George Neuner
<[email protected]> wrote:
Seriously, no thoughts on block comments? Shouldn't every language be
forced to use /* */
Using pairs of special characters for *any* purpose, from ** for exponentiation in FORTRAN onwards, and including <= and >= as
relational operators, vastly complicates the syntax decoding of
languages.
Of course, even at
http://www.quadibloc.com/comp/lngint.htm
I couldn't _entirely_ avoid compounds of special characters. So what I
did was adopt systematic rules:
Only ?, @, and & can begin multi-character strings which are to be
treated as a single character. @" is used for //, and @< and @> are
used for /* and */. (The @ sign is generally used only for tokens
which are "outside" of actual statements, at the upper level of the
syntax.) &LE and &GE are used for <= and >=.
Incidentally, C didn't invent /* and */; it got them from PL/I,
although no doubt PL/I got them from some representation or other of
ALGOL.
John Savard
Niklas Holsti <[email protected]d> writes:
On 2026-10-13 13:27, Anton Ertl wrote:
Niklas Holsti <[email protected]d> writes:
Forth originated in astronomy, if I remember correctly. I do remember
that in an astronomy-domain Forth program, where it was often necessary >>>> to transform the celestial coordinates of stars from the standard
catalog epoch (the start of the year 1950) to the current time, the word >>>> 1950.0 was defined to perform that transformation.
That is unlikely, as explained below.
I am rather sure that my memory is correct.
In early systems fixed-point was used a lot, and 1950.0 isThe Forth program in question represented angles as fixed-point
interpreted as 19500 with a scale factor of 1/10.
fractions (0 <= x < 1) of a full circle. I don't remember the number of
bits used.
Yes, that was customary for angles, as mentioned in the HOPL-II paper
"The Evolution of Forth" <https://www.forth.com/resources/forth-programming-language/>:
On Wed, 14 Oct 2026 12:14:30 -0400, George Neuner
<[email protected]> wrote:
Seriously, no thoughts on block comments? Shouldn't every language be >forced to use /* */
Using pairs of special characters for *any* purpose, from ** for exponentiation in FORTRAN onwards, and including <= and >= as
relational operators, vastly complicates the syntax decoding of
languages.
Of course, even at--- Synchronet 3.22a-Linux NewsLink 1.2
http://www.quadibloc.com/comp/lngint.htm
I couldn't _entirely_ avoid compounds of special characters. So what I
did was adopt systematic rules:
Only ?, @, and & can begin multi-character strings which are to be
treated as a single character. @" is used for //, and @< and @> are
used for /* and */. (The @ sign is generally used only for tokens
which are "outside" of actual statements, at the upper level of the
syntax.) &LE and &GE are used for <= and >=.
Incidentally, C didn't invent /* and */; it got them from PL/I,
although no doubt PL/I got them from some representation or other of
ALGOL.
John Savard
Old FORTRAN is difficult to parse because
a) it did not separate the language into lexical and syntax levels
b) it was white space oblivious except in quoted strings
c) there were no reserved words - keywords were statement
position and sometimes semantic context dependent.
d) statements were based on punch cards BUT because
continuation cards used the absence of a C in column 6
to indicate the start of a new statement, parsers have to
always look ahead to see in the next card has a C and
then "unget" the card if not.
The classic ambiguous parsing example being
DO I = 1,100
DOI = 1.100
depends on the presence or absence of a "naked comma" later in the
statement to decide whether there is a keyword DO and a variable I,
or a variable DOI. A "naked comma" is a comma not enclosed inside
a quoted character or string, or inside a possibly nested parenthesis.
People learned a lot of what not to do in language design
from old FORTRAN.
On the other hand using « » ÷ ≠ ≤ ≥ ≡ would destroy the 1 char == 1 byte
philosophy of compilers and assemblers.
EricP <[email protected]> writes:
Old FORTRAN is difficult to parse because
a) it did not separate the language into lexical and syntax levels
b) it was white space oblivious except in quoted strings
c) there were no reserved words - keywords were statement
position and sometimes semantic context dependent.
d) statements were based on punch cards BUT because
continuation cards used the absence of a C in column 6
to indicate the start of a new statement, parsers have to
always look ahead to see in the next card has a C and
then "unget" the card if not.
Given that the original FORTRAN worked on an IBM 704, I think that the designers made it as easy (and with as little memory) to parse as they
could, given the state of the art at the time. Concerning the points:
a) I guess that the division into lexical and syntax levels had not
been developed at the time;
and even later, e.g., Wirth does not
make this distinction in his language definitions, using EBNF down
to the lexical level (although he does define reserved words).
b) Without this distinction, if you ignore white space at all, you
either ignore it everywhere, or give it a special role in your
syntax. Ignoring is probably simpler.
c) The programming language community took quite a while until it
arrived at reserved words.
E.g., while Algol 60 was specified in
BNF, being modern in that regard, it does not have reserved words,
just symbols that are distinct from all identifiers; i.e., you
could have an identifier like IF in Algol 60, distinct from the
symbol 'IF', and it's up to the implementation how to distinguish
them. Anyway, without a) reserved words make no sense.
d) Does the original FORTRAN support continuation cards?
Given that the original FORTRAN worked on an IBM 704, I think that the designers made it as easy (and with as little memory) to parse as they
could, given the state of the art at the time. Concerning the points:
EricP <[email protected]> writes:
Old FORTRAN is difficult to parse because
a) it did not separate the language into lexical and syntax levels
b) it was white space oblivious except in quoted strings
c) there were no reserved words - keywords were statement
position and sometimes semantic context dependent.
d) statements were based on punch cards BUT because
continuation cards used the absence of a C in column 6
to indicate the start of a new statement, parsers have to
always look ahead to see in the next card has a C and
then "unget" the card if not.
Given that the original FORTRAN worked on an IBM 704, I think that the designers made it as easy (and with as little memory) to parse as they
could, given the state of the art at the time. Concerning the points:
a) I guess that the division into lexical and syntax levels had not
been developed at the time; and even later, e.g., Wirth does not
make this distinction in his language definitions, using EBNF down
to the lexical level (although he does define reserved words).
b) Without this distinction, if you ignore white space at all, you
either ignore it everywhere, or give it a special role in your
syntax. Ignoring is probably simpler.
c) The programming language community took quite a while until it
arrived at reserved words. E.g., while Algol 60 was specified in
BNF, being modern in that regard, it does not have reserved words,
just symbols that are distinct from all identifiers; i.e., you
could have an identifier like IF in Algol 60, distinct from the
symbol 'IF', and it's up to the implementation how to distinguish
them. Anyway, without a) reserved words make no sense.
d) Does the original FORTRAN support continuation cards?
The classic ambiguous parsing example being
DO I = 1,100
DOI = 1.100
depends on the presence or absence of a "naked comma" later in the
statement to decide whether there is a keyword DO and a variable I,
or a variable DOI. A "naked comma" is a comma not enclosed inside
a quoted character or string, or inside a possibly nested parenthesis.
My guess as to how this stuff was parsed:
Deal with the first columns as appropriate. For the rest: Remove any
white space. Use a backtracking parser that tries to match a rule at
a time; for the DO statement, simply compare the start with "DO". If
a rule does not match, look at the next rule.
The result is messy, especially when you want to extend the language.
People learned a lot of what not to do in language design
from old FORTRAN.
And probably also what to do. Backus was on the Algol-60 committee,
and the B in BNF stands for his name. I would not be surprised if the structure of the parser in the FORTRAN compiler gave him ideas for
BNF.
- anton
Anton Ertl <[email protected]> schrieb:
and even later, e.g., Wirth does not
make this distinction in his language definitions, using EBNF down
to the lexical level (although he does define reserved words).
Also in modern language definition. Separating lexing and parsing
is a matter of convenience, especially since regular expressions
have been introduced, and speed.
b) Without this distinction, if you ignore white space at all, you
either ignore it everywhere, or give it a special role in your
syntax. Ignoring is probably simpler.
The added complication of separate handling of whitespace is minor.
For the original FORTRAN, they skipped them because card punch operators >often made mistakes with spaces.
c) The programming language community took quite a while until it
arrived at reserved words.
Reserved words make the compiler writer's job easier (somewhat,
but not tremendously), but not having them is a solved problem.
Users are left holding the bag if a new version of the language
introduces new keywords.
d) Does the original FORTRAN support continuation cards?
Yes, it would have extremely restricted otherwise.
On 2026-Oct-15 02:17, Anton Ertl wrote:
My guess as to how this stuff was parsed:
Deal with the first columns as appropriate. For the rest: Remove any
white space. Use a backtracking parser that tries to match a rule at
a time; for the DO statement, simply compare the start with "DO". If
a rule does not match, look at the next rule.
The result is messy, especially when you want to extend the language.
A backtracking parser sounds like way overkill.
I am under the impression that many Fortran 77 compilers used
hand written lexer and a recursive decent parser.
The parser passes some local context to the lexer to let it know,
for example, which set of keywords are active at that point.
Anyway, that is how I would do it.
There is a method called "Sale's Algorithm" which was published
in CACM in the 1960's which describes how to lex up to Fortran 77.
After that it is pretty standard AST processing.
The only semantic difference I can think of immediately is
Fortran's parenthesis rule that forces an order of evaluation
and would interact with optimizations like common subexpressions.
Thomas Koenig <[email protected]> writes:------------
Anton Ertl <[email protected]> schrieb:
What in FORTRAN makes single 66-char lines (plus line number)
extremely restricted?
- anton--- Synchronet 3.22a-Linux NewsLink 1.2
On 2026-Oct-15 02:17, Anton Ertl wrote:
EricP <[email protected]> writes:
Old FORTRAN is difficult to parse because
a) it did not separate the language into lexical and syntax levels
b) it was white space oblivious except in quoted strings
c) there were no reserved words - keywords were statement
position and sometimes semantic context dependent.
d) statements were based on punch cards BUT because
continuation cards used the absence of a C in column 6
to indicate the start of a new statement, parsers have to
always look ahead to see in the next card has a C and
then "unget" the card if not.
Given that the original FORTRAN worked on an IBM 704, I think that the
designers made it as easy (and with as little memory) to parse as they
could, given the state of the art at the time. Concerning the points:
a) I guess that the division into lexical and syntax levels had not
been developed at the time; and even later, e.g., Wirth does not
make this distinction in his language definitions, using EBNF down
to the lexical level (although he does define reserved words).
b) Without this distinction, if you ignore white space at all, you
either ignore it everywhere, or give it a special role in your
syntax. Ignoring is probably simpler.
c) The programming language community took quite a while until it
arrived at reserved words. E.g., while Algol 60 was specified in
BNF, being modern in that regard, it does not have reserved words,
just symbols that are distinct from all identifiers; i.e., you
could have an identifier like IF in Algol 60, distinct from the
symbol 'IF', and it's up to the implementation how to distinguish
them. Anyway, without a) reserved words make no sense.
d) Does the original FORTRAN support continuation cards?
Yes. Fortran's continuation card is LL(1) and behaves the same as
an ISA with start bits - you have to look ahead to find out
that you didn't need to look ahead, and unget the card.
Statement terminators like ; act like ISA stop bits and don't need lookahead.
The classic ambiguous parsing example being
DO I = 1,100
DOI = 1.100
depends on the presence or absence of a "naked comma" later in the
statement to decide whether there is a keyword DO and a variable I,
or a variable DOI. A "naked comma" is a comma not enclosed inside
a quoted character or string, or inside a possibly nested parenthesis.
My guess as to how this stuff was parsed:
Deal with the first columns as appropriate. For the rest: Remove any
white space. Use a backtracking parser that tries to match a rule at
a time; for the DO statement, simply compare the start with "DO". If
a rule does not match, look at the next rule.
The result is messy, especially when you want to extend the language.
A backtracking parser sounds like way overkill.
I am under the impression that many Fortran 77 compilers used
hand written lexer and a recursive decent parser.
The parser passes some local context to the lexer to let it know,
for example, which set of keywords are active at that point.
Anyway, that is how I would do it.
The first thing a Fortran compiler does is scan ahead loading card
statements into a card buffer array from the (possibly nested INCLUDE)
source files until it sees a non-continue card, which it ungets.
Then it checks the format of the cards is ok and extracts things like
a label from columns 2:5.
Compacting out comments and white space is unnecessary -
the hand written lexer knows to skip over them.
There is a method called "Sale's Algorithm" which was published
in CACM in the 1960's which describes how to lex up to Fortran 77.
The parser knows which statement it is working on so can tell the
lexer what options it should handle at the point.
There could be a post pass over the AST to patch up ambiguous
constructs that require semantic context to fix.
After that it is pretty standard AST processing.
The only semantic difference I can think of immediately is
Fortran's parenthesis rule that forces an order of evaluation
and would interact with optimizations like common subexpressions.
As for convenience, for various compilers I read that either the
scanner or the parser (or both) were hand-written (I don't remember
which one was more frequent).
It certainly does not seem much work to
write a scanner for a typical programming language by hand (you
recognize keywords by table lookup of things that will be classified
as identifiers if they are not in the table). For the parser of a
language that is not LL(1), it's more work, but writing it by hand
allows you to do various things that, e.g., bison does not support
(ANTLR has support for "syntactic predicates" and "semantic
predicates", which probably is good enough for a lot of cases; it was
written with C++ in mind).
In article <[email protected]>,
MitchAlsup <[email protected]d> wrote:
[email protected] (Dan Cross) posted:
First off: thank you dan for a well done explanation.
In article <[email protected]>,--------snip of what I understand clearly------------
MitchAlsup <[email protected]d> wrote:
Terje Mathisen <[email protected]> posted:
Dan Cross wrote:---------
My old asm/C brain _knows_ that a slice of something with a known
address and total length is effectively the same thing as an unmutable
array.
Can I get a definition of what mutable in Rust means ??
So this will fail to compile:
? let mut foo = something();
? let foomut = &mut foo; // Ok: only mutable
? let barmut = &mut foo // Fail: a mut ref to foo exists
Is this supposed to work across threads ?? so, only 1 thread has write
access at any given point in time.
Yes, that's right.
If so, how does one thread hand-off mutability to another ??
Typically, you use a mutual exclusion primitive of one kind or
another to serialize access to mutable state. For example, the
standard library includes the usual complement of Mutex,
reader/writer locks, condition variables, barriers, reference
counters, thread-safe channels, and so on. For bare-metal
programs we have to implement these ourselves, but there are
some third-party implementations that can help (for example, the
`spin` crate implements spinlocks, providing, an interface that
looks close to `std::sync::Mutex`).
Something I should have mentioned earlier is that Rust object
has a single, well-defined owner, and that references to that
object are borrowed _from the owner_.
I was expecting one "thread" to hold (own?) the object in question
and was unsure how mutablility was handed out across other threads.
I had imagined that then current mut owner had its 'pointer' go
out of block and that somehow his no longer having a mutable ref
to the object somehow got propagated automagically by his leaving
of the block {which seems untenable}.
falling back on standard ways of passing this stuff around adds hope.
{{less Ada rendezvous like}}
So, using `Mutex` as an example, we make the `Mutex` itself the
owner of the object: the `.lock()` method lets threads borrow
mutable references to the object, and the serialization inherent
in the Mutex type and the locking protocol enforces the
"mut ^ immut" semantics across threads.
Since I am a CPU guy, I would like to see the asm from any modern >architecture perform this handoff ! <please> ?
Similarly, a `RwLock` can hand out multiple immutable refs to
the object, but keeps track of them, and only lets one borrower
access a `mut ref` at a time, and only when there are no
outstanding immutable refs to it.
Sounds like reference counting...
In fact, it's slightly more complex in that `mutex.lock()`
actually returns a proxy object that holds onto the reference;
its return type is actually, `LockResult<LockGuard<'_, T>>`,
where `'_` is an anonymous lifetime, and `T` is the type of the
thing protected by the `Mutex`. `LockResult` lets someone
detect a lock that has been poisoned; the `LockGuard` is the
thing that holds the ref. When it goes otu of scope, it
automatically unlocks the mutex, and it implements a trait
called `Deref` to forward uses on to the ref to the locked
object.
Another level of indirection to handle a more complicated issue...
This all intersects with the `Send`/`Sync` stuff; `Mutex` is
`Sync`, and `Mutex` objects themselves are treated as immutable.
So to share access to a `Foo` between threads, I use something
like a `Mutex<Foo>`. This is an example of "Interior
Mutability", where something immutable (the `Mutex` object
itself) can hold onto something that is mutable.
This raises a question: if all this machinery is required for
safe access to shared data across threads anyway, then why all
the language-level rules? The answer is hinted at by the mutex
object itself is immutable, we can hand out references to it
easily. Since it owns the (mutable) object the lock protects,
it can decide how to hand out references to the object.
We have the same problems with coherent caches and there are 3
normal solutions {MESI, MOESI, and Directories}
[snip]
I am under the impression that many Fortran 77 compilers used
hand written lexer and a recursive decent parser.
The parser passes some local context to the lexer to let it know,
for example, which set of keywords are active at that point.
Anyway, that is how I would do it.
The reason why I think of a backtracking parser is that it seems the
easiest way to be able to handle both
DO101I=1.100
and
DO101I=1,100
There is a method called "Sale's Algorithm" which was published
in CACM in the 1960's which describes how to lex up to Fortran 77.
Interesting, but did the FORTRAN team come up with that?
As for convenience, for various compilers I read that either the
scanner or the parser (or both) were hand-written (I don't remember
which one was more frequent). ...
People learned a lot of what not to do in language design
from old FORTRAN.
EricP <[email protected]> writes:
On 2026-Oct-15 02:17, Anton Ertl wrote:
My guess as to how this stuff was parsed:
Deal with the first columns as appropriate. For the rest: Remove any
white space. Use a backtracking parser that tries to match a rule at
a time; for the DO statement, simply compare the start with "DO". If
a rule does not match, look at the next rule.
The result is messy, especially when you want to extend the language.
A backtracking parser sounds like way overkill.
A backtracking parser is relatively easy and the memory needs for
parsing just a single statement are small, too. For an example of how
short a parser generator for a backtracking parser framework can be,
look at the second example in
<https://bernd-paysan.de/screenful.html>.
I am under the impression that many Fortran 77 compilers used
hand written lexer and a recursive decent parser.
The parser passes some local context to the lexer to let it know,
for example, which set of keywords are active at that point.
Anyway, that is how I would do it.
The reason why I think of a backtracking parser is that it seems the
easiest way to be able to handle both
DO101I=1.100
and
DO101I=1,100
(examples given on <https://en.wikipedia.org/wiki/Fortran#Fixed_layout_and_punched_cards>)
There is a method called "Sale's Algorithm" which was published
in CACM in the 1960's which describes how to lex up to Fortran 77.
Interesting, but did the FORTRAN team come up with that? According to <https://fortran-lang.discourse.group/t/has-anyone-played-with-sales-fortran-classification-algorithm-lately/7802>
the algorithm was published in 1971.
After that it is pretty standard AST processing.
The only semantic difference I can think of immediately is
Fortran's parenthesis rule that forces an order of evaluation
and would interact with optimizations like common subexpressions.
Searching around, I found little about "order of evaluation" when it
comes to parenthesis in FORTRAN, although I remember hearing or
reading about the significance of parentheses in FORTRAN several
times. But does it have any relevance beyond determining that a+(b+c)
is evalutated as
t=b+c
r=a+t
rather than
t=a+b
r=t+c
If not, that rule just disallows reassociation, but reassociation is
not something I have seen in common subexpression elimination (or,
more generally, partial redundancy elimination) algorithms anyway.
EricP <[email protected]> writes:
in CACM in the 1960's which describes how to lex up to Fortran 77.There is a method called "Sale's Algorithm" which was published
Interesting, but did the FORTRAN team come up with that? According to <https://fortran-lang.discourse.group/t/has-anyone-played-with-sales-fortran-classification-algorithm-lately/7802>
the algorithm was published in 1971.
DEC F77 had a number of enhancements which made the language
more friendly, like tab in column 1 skips to the start of a
statement
or ! anywhere is a comment to end of line.
These were adopted by many other F77 compiler vendors
presumably because they wanted to get their customers.
Parenthesis can be used to force an order of execution.
The parenthesis ordering rule is also data type dependent.
It applies to integer, logical (also integer) and real but not strings.
#1 a + b + c Unordered
#2 (a+b) + c a+b before +c
#3 a + (b+c) b+c before +a
This would interact with common subexpression elimination.
Above would not be able to optimize all 3 expressions to 1.
It can replace #1 with #2 or #3, but not both.
I'm not sure but I think parenthesis also could block optimizations
like a*b+a*c => a*(b+c) but (a*b)+(a*c) might not be optimized.
According to EricP <[email protected]>:
I am under the impression that many Fortran 77 compilers used
hand written lexer and a recursive decent parser.
The parser passes some local context to the lexer to let it know,
for example, which set of keywords are active at that point.
Anyway, that is how I would do it.
I wrote a production FORTRAN 77 compiler. It had a hand written lexer
and a yacc parser.
It made a prepass over each statement to combine continuation cards and to tell
whether it's an assignment statement or statement function (they're syntactically the same) or something else. Once it knows that, the lexer is straightforward using feedback from the parser to know what tokens are allowed
at each point. As I recall there were about a dozen states, most of which were
simple, e.g., after a DO keyword the next thing has to be a digit string which is a statement number.
The prepass was needed to disambiguate DO10I=1.10 and also to handle stuff like this:
10 FORMAT(I10,A5) // a format statement
20 FORMAT(I10,A5) = 42 // an assignment statement
According to Anton Ertl <[email protected]>:
Interesting, but did the FORTRAN team come up with that?I think we can assume they did since it's by far the easiest way to
parse card image FORTRAN. If anyone really cares I believe the
assembler source of some early compilers is available online.
Which could be written::
20 FORMAT(I10,A5)
-
c comment getting in the way
-
- = 42
EricP <[email protected]> schrieb:
DEC F77 had a number of enhancements which made the language
more friendly, like tab in column 1 skips to the start of a
statement
That didn't make it into the language, and also makes calculation
of the line length a bit awkward. When should the line end?
Remember, collumn 73-80 are ignored.
or ! anywhere is a comment to end of line.
Was that a DEC extension? At least it was adapted in Fortran 90.
These were adopted by many other F77 compiler vendors
presumably because they wanted to get their customers.
They are a rat's nest. Yes, many were convenient, but compilers
more or less have to keep supporting them for legacy code.
gfortran has -fdec, which in turn enables around ten extensions,
but not everything is implemented, for example variable formats
or the infamous Q format descriptor.
And, of course, the (in)famous PARAMETER X=20 snafu.
Parenthesis can be used to force an order of execution.
The parenthesis ordering rule is also data type dependent.
It applies to integer, logical (also integer) and real but not strings.
Not sure what use they would be for strings... but where did you get
that information from?
#1 a + b + c Unordered
#2 (a+b) + c a+b before +c
#3 a + (b+c) b+c before +a
This would interact with common subexpression elimination.
Above would not be able to optimize all 3 expressions to 1.
It can replace #1 with #2 or #3, but not both.
I'm not sure but I think parenthesis also could block optimizations
like a*b+a*c => a*(b+c) but (a*b)+(a*c) might not be optimized.
The rule is simple: In Fortran, parameters have to be honored,
otherwise the rules of standard mathematics apply. So,
a + b - a can be optimized to b.
On 2026-Oct-17 10:58, Thomas Koenig wrote:
EricP <[email protected]> schrieb:
DEC F77 had a number of enhancements which made the language
more friendly, like tab in column 1 skips to the start of a
statement
That didn't make it into the language, and also makes calculation
of the line length a bit awkward. When should the line end?
Remember, collumn 73-80 are ignored.
That shouldn't be a problem.
A tab in columns 1 to 6 meant jump to column 7, the statement start.
DEC also optionally allowed lines up to 132 characters -
the full width of a VT100 in widescreen mode!
or ! anywhere is a comment to end of line.
Was that a DEC extension? At least it was adapted in Fortran 90.
Yes
There were lots more. 32 character variables (up from the standard 8).
DEC F77 used a stack and allowed recursion if you were careful
whereas IBM F77 did not (it used static allocation for all local
variables so recursion stomped on their values).
These were adopted by many other F77 compiler vendors
presumably because they wanted to get their customers.
They are a rat's nest. Yes, many were convenient, but compilers
more or less have to keep supporting them for legacy code.
gfortran has -fdec, which in turn enables around ten extensions,
but not everything is implemented, for example variable formats
or the infamous Q format descriptor.
And, of course, the (in)famous PARAMETER X=20 snafu.
I don't know that one. Do tell...
Parenthesis can be used to force an order of execution.
The parenthesis ordering rule is also data type dependent.
It applies to integer, logical (also integer) and real but not strings.
Not sure what use they would be for strings... but where did you get
that information from?
I have a 1988 copy of the DEC Fortran manual.
I was looking at it for a specific set of parenthesis rules
but no such luck. But it did say that parenthesis do not affect
the value of a character string.
My point being that whereas in many languages the parser can
toss parenthesis, in F77 expressions they may be significant,
and that significance may be semantic dependent.
#1 a + b + c Unordered
#2 (a+b) + c a+b before +c
#3 a + (b+c) b+c before +a
This would interact with common subexpression elimination.
Above would not be able to optimize all 3 expressions to 1.
It can replace #1 with #2 or #3, but not both.
I'm not sure but I think parenthesis also could block optimizations
like a*b+a*c => a*(b+c) but (a*b)+(a*c) might not be optimized.
The rule is simple: In Fortran, parameters have to be honored,
otherwise the rules of standard mathematics apply. So,
a + b - a can be optimized to b.
Yes but determining if two expressions are equivalent
and therefore candidates for elimination can require
some massaging first.
#4 a + ((b+c))
the first ( is significant, the second ( is redundant,
and #4 should match #3 and not #2.
But the following string expressions are all equivalent
#5 ('ABC')//('DE')//'F'
#6 'ABC'//('DE'//'F')
#7 'ABC'//'DE'//'F'
On Wed, 14 Oct 2026 15:45:41 -0400, EricP
<[email protected]> wrote:
People learned a lot of what not to do in language design
from old FORTRAN.
Oh, yes. My language, EXALT, which I previously called FALCON, before learning the name was taken (several times), did learn from the
DO 7 I = 1.100
problem.
It still takes stuff from FORTRAN.
In it, a line with _any_ letter in Column 1 is a comment. So the
comments can start two characters earlier!
But assignment statements have to start with the keyword LET... or its abbreviation, which is just ".".
Also FORTRAN-like: no semicolons needed at the end of the line.
John Savard
I like the C/C++ style comments. It is handy to have the two types of >comments. // and /* */. I use // for almost all comments, then /* */ to >comment out a block of code for debugging.
I included an equivalent of // in my language; I think I used @' or @"
for it, so as to fit into my scheme of strict prefix-property
decoding.
On 2026-Oct-17 10:58, Thomas Koenig wrote:
EricP <[email protected]> schrieb:
DEC F77 had a number of enhancements which made the language
more friendly, like tab in column 1 skips to the start of a
statement
That didn't make it into the language, and also makes calculation
of the line length a bit awkward. When should the line end?
Remember, collumn 73-80 are ignored.
That shouldn't be a problem.
A tab in columns 1 to 6 meant jump to column 7, the statement start.
DEC also optionally allowed lines up to 132 characters -
the full width of a VT100 in widescreen mode!
or ! anywhere is a comment to end of line.
Was that a DEC extension? At least it was adapted in Fortran 90.
Yes
There were lots more. 32 character variables (up from the standard 8).
DEC F77 used a stack and allowed recursion if you were careful
whereas IBM F77 did not (it used static allocation for all local
variables so recursion stomped on their values).
These were adopted by many other F77 compiler vendors
presumably because they wanted to get their customers.
They are a rat's nest. Yes, many were convenient, but compilers
more or less have to keep supporting them for legacy code.
gfortran has -fdec, which in turn enables around ten extensions,
but not everything is implemented, for example variable formats
or the infamous Q format descriptor.
And, of course, the (in)famous PARAMETER X=20 snafu.
I don't know that one. Do tell...
Parenthesis can be used to force an order of execution.
The parenthesis ordering rule is also data type dependent.
It applies to integer, logical (also integer) and real but not strings.
Not sure what use they would be for strings... but where did you get
that information from?
I have a 1988 copy of the DEC Fortran manual.
I was looking at it for a specific set of parenthesis rules
but no such luck. But it did say that parenthesis do not affect
the value of a character string.
My point being that whereas in many languages the parser can
toss parenthesis, in F77 expressions they may be significant,
and that significance may be semantic dependent.
#1 a + b + c Unordered
#2 (a+b) + c a+b before +c
#3 a + (b+c) b+c before +a
This would interact with common subexpression elimination.
Above would not be able to optimize all 3 expressions to 1.
It can replace #1 with #2 or #3, but not both.
I'm not sure but I think parenthesis also could block optimizations
like a*b+a*c => a*(b+c) but (a*b)+(a*c) might not be optimized.
The rule is simple: In Fortran, parameters have to be honored,
otherwise the rules of standard mathematics apply. So,
a + b - a can be optimized to b.
Yes but determining if two expressions are equivalent
and therefore candidates for elimination can require
some massaging first.
#4 a + ((b+c))
the first ( is significant, the second ( is redundant,
and #4 should match #3 and not #2.
But the following string expressions are all equivalent
#5 ('ABC')//('DE')//'F'
#6 'ABC'//('DE'//'F')
#7 'ABC'//'DE'//'F'
On Sun, 18 Oct 2026 22:54:24 -0400, Robert Finch <[email protected]>
wrote:
I like the C/C++ style comments. It is handy to have the two types of >>comments. // and /* */. I use // for almost all comments, then /* */ to >>comment out a block of code for debugging.
It should be noted that C originally only had /* and */. // was added
with C++, and then proved so popular that it was then added to some
versions of C.
DEC F77 used a stack and allowed recursion if you were careful
whereas IBM F77 did not (it used static allocation for all local
variables so recursion stomped on their values).
OS/360 calling conventions is one thing they got wrong.
According to Thomas Koenig <[email protected]>:
DEC F77 used a stack and allowed recursion if you were careful
whereas IBM F77 did not (it used static allocation for all local
variables so recursion stomped on their values).
OS/360 calling conventions is one thing they got wrong.
My PDP-11 Unix F77 used the C compiler's back end, so recursion
worked fine. I made variables automatic (stack) unless you used
a SAVE statement, which made sense in the PDP-11's limited address space.
It is quite possible to do recursion with the OS/360 calling convention. Their PL/I compilers did it and I believe the various ALGOL compilers,
which had to support recursion, also used the standard calling sequence.
On 20/10/2026 04:50, John Levine wrote:
According to Thomas Koenig <[email protected]>:
DEC F77 used a stack and allowed recursion if you were careful
whereas IBM F77 did not (it used static allocation for all local
variables so recursion stomped on their values).
OS/360 calling conventions is one thing they got wrong.
My PDP-11 Unix F77 used the C compiler's back end, so recursion
worked fine. I made variables automatic (stack) unless you used
a SAVE statement, which made sense in the PDP-11's limited address space.
It is quite possible to do recursion with the OS/360 calling convention.
Their PL/I compilers did it and I believe the various ALGOL compilers,
which had to support recursion, also used the standard calling sequence.
Brian Wichmann (of Whetstone Benchmark fame) gives the following
figures for Ackermann's function, a stress test for recursion.
ALGOL 60, by architecture, mean orders/call, * = estimate
Bill Findlay <[email protected]> schrieb:
On 20/10/2026 04:50, John Levine wrote:
According to Thomas Koenig <[email protected]>:
DEC F77 used a stack and allowed recursion if you were careful
whereas IBM F77 did not (it used static allocation for all local
variables so recursion stomped on their values).
OS/360 calling conventions is one thing they got wrong.
My PDP-11 Unix F77 used the C compiler's back end, so recursion
worked fine. I made variables automatic (stack) unless you used
a SAVE statement, which made sense in the PDP-11's limited address space. >>>
It is quite possible to do recursion with the OS/360 calling convention. >>> Their PL/I compilers did it and I believe the various ALGOL compilers,
which had to support recursion, also used the standard calling sequence. >>>
Brian Wichmann (of Whetstone Benchmark fame) gives the following
figures for Ackermann's function, a stress test for recursion.
ALGOL 60, by architecture, mean orders/call, * = estimate
I understand "ALGOL 60", but not the rest.
What orders?
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
System/3?0, by language and compiler
ALGOL 60, Edinburgh Uni. /360 21.0
PL/I OPT v1.2.1, IBM /360 61 *
ALGOL W, Stanford Uni. /360 74 *
ALGOL 60, Delft Uni. /360 142 *
PL/I F level v5.4, IBM /360 212 *
ALGOL 60, IBM F level /360 820 *
ALGOL F seems to have been written with the
attitude, you want an Algol compiler? Here's an Algol compiler, suck it up.
On Wed, 21 Oct 2026 03:28:44 -0000 (UTC), John Levine
<[email protected]> wrote:
ALGOL F seems to have been written with the
attitude, you want an Algol compiler? Here's an Algol compiler, suck it up.
Yes. Unlike ALGOL W you had to indicate the keywords by putting quote
marks around them - although some halfway-serious ALGOL 60 compilers
for other architectures also did this.
In article <[email protected]>,
John Savard <[email protected]> wrote:
On Wed, 21 Oct 2026 03:28:44 -0000 (UTC), John Levine
<[email protected]> wrote:
ALGOL F seems to have been written with theYes. Unlike ALGOL W you had to indicate the keywords by putting quote
attitude, you want an Algol compiler? Here's an Algol compiler, suck it up. >>
marks around them - although some halfway-serious ALGOL 60 compilers
for other architectures also did this.
I don't believe that is true. The descriptions of AGOL W that
are publicly available do not indicate that quote marks were
required around keywords; I have only seen that on Algol 60
code.
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (Scott Lurndal)The US-centrism is strong in these replies.
wrote:
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead referred
to as its "order code".
On 20/10/2026 23:49, John Savard wrote:
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (Scott Lurndal)The US-centrism is strong in these replies.
wrote:
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead referred
to as its "order code".
Sigh. (I have taken that over from Nick.)
Bill Findlay <[email protected]> writes:
On 20/10/2026 23:49, John Savard wrote:
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (Scott Lurndal)The US-centrism is strong in these replies.
wrote:
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead referred
to as its "order code".
Sigh. (I have taken that over from Nick.)
Perhaps you might, then, enlighten your yank
cousins by providing the UK viewpoint.
On 22/10/2026 01:02, Scott Lurndal wrote:
Bill Findlay <[email protected]> writes:
On 20/10/2026 23:49, John Savard wrote:
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (Scott Lurndal)The US-centrism is strong in these replies.
wrote:
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead referred
to as its "order code".
Sigh. (I have taken that over from Nick.)
Perhaps you might, then, enlighten your yank
cousins by providing the UK viewpoint.
I do, from time to time, and am generally ignored.
Bill Findlay <[email protected]> posted:
On 22/10/2026 01:02, Scott Lurndal wrote:
Bill Findlay <[email protected]> writes:
On 20/10/2026 23:49, John Savard wrote:
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (Scott Lurndal) >>>>> wrote:The US-centrism is strong in these replies.
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead referred >>>>> to as its "order code".
Sigh. (I have taken that over from Nick.)
Perhaps you might, then, enlighten your yank
cousins by providing the UK viewpoint.
I do, from time to time, and am generally ignored.
That is ALMOST the Casandra Syndrome:: "always tell the truth,
nobody ever believes"
On 23/10/2026 01:27, MitchAlsup wrote:
Bill Findlay <[email protected]> posted:
On 22/10/2026 01:02, Scott Lurndal wrote:
Bill Findlay <[email protected]> writes:
On 20/10/2026 23:49, John Savard wrote:
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (ScottThe US-centrism is strong in these replies.
Lurndal) wrote:
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead
referred to as its "order code".
Sigh. (I have taken that over from Nick.)
Perhaps you might, then, enlighten your yank
cousins by providing the UK viewpoint.
I do, from time to time, and am generally ignored.
That is ALMOST the Casandra Syndrome:: "always tell the truth,
nobody ever believes"
Quite so.
However, it all goes into an archive and will continue
being available to those with curiosity and broad minds.
On 23/10/2026 01:27, MitchAlsup wrote:
Bill Findlay <[email protected]> posted:
On 22/10/2026 01:02, Scott Lurndal wrote:
Bill Findlay <[email protected]> writes:
On 20/10/2026 23:49, John Savard wrote:
On Tue, 20 Oct 2026 20:44:45 GMT, [email protected] (Scott Lurndal) >>>>>> wrote:The US-centrism is strong in these replies.
Archaic terminology for "instructions".
The early Burroughs systems (Electrodata 220, Burroughs 300,
B3500) all called them 'orders' rather than instructions.
In fact, the processor test suite for the B3500 and successors
was called all-orders.
At one time, the _instruction set_ of a computer was instead referred >>>>>> to as its "order code".
Sigh. (I have taken that over from Nick.)
Perhaps you might, then, enlighten your yank
cousins by providing the UK viewpoint.
I do, from time to time, and am generally ignored.
That is ALMOST the Casandra Syndrome:: "always tell the truth,
nobody ever believes"
Quite so.
However, it all goes into an archive and will continue
being available to those with curiosity and broad minds.
| Sysop: | DaiTengu |
|---|---|
| Location: | Appleton, WI |
| Users: | 1,142 |
| Nodes: | 10 (0 / 10) |
| Uptime: | 94:08:10 |
| Calls: | 14,704 |
| Calls today: | 2 |
| Files: | 186,497 |
| D/L today: |
2,661 files (886M bytes) |
| Messages: | 2,551,683 |