The Periodic Table of Rust Types
cosmic.mearie.org
cosmic.mearie.org
It's a great way to present it, but like Scala's infamous "Periodic Table of Dispatch Operators", stuff like is rather off putting. Why use crude sigils when you could just as well use easily understood keywords (like ref, out, unsafe etc. in C-Sharp)?
let x = ~"Thing"; //old code
let x = box Thing // new code probably
Note: I said probably because it's probably because:A) Not sure it will be implemented
B) Not sure if that will be syntax
--- PS. Any link to dispatch operator periodic table?
Also I take your scala and raise you a Perl: http://glyphic.s3.amazonaws.com/ozone/mark/periodic/Periodic...
The Dispatch periodic table is here:
Out of curiosity why are they making the change? And will they add the `let x = box Thing` syntax or as an optional addition to the language or will it fully replace the old `let = ~Thing` syntax?
Additionally, this table is different from the tables of Scala dispatch operators, Perl operators, &c because of its regularity—it has two axes and a good deal of the information presented is a straightforward function of a cell's position in the table[1]. For example, the entry in the Owned column and the String row is going to be an... owned string, which is going to be prefixed with ~ (like every cell in the Owned column) and have a base type of str (like every cell in the String row). This is a far cry from the table of Scala's dispatch operators, in which there's no consistent indication of what a given operator will necessarily do aside from a general grouping of like operators.
[^1, edit]: That isn't to say that ALL the information is a (predictable) function of a cell's position in the table. Some of that irregularity comes from arguably expected consequences of Rust semantics (e.g. of course you can't have a bare string, because you can only have bare values whose sizes are known in advance, and you don't know how long a string will be) while some is genuinely arbitrary, like the syntactic sugar for functions.
So… would a dialect of Ruby without side effects be called Corundum?
I may be wrong, but my understanding is that Rust's constants are actually constant, and proved as such by the compiler, which is a major difference over C++.
I don't know how much of a speed difference it would actually make in practice, but it bothers me that I cannot express this in C++.
Yes and that's a huge trap, which is why I'm so annoyed that D has copied 'const' from C++ instead of renaming it to 'view' or 'read'..
#include <iostream>
void f(const int* var)
{
*var = 42;
}
int main()
{
int* ptr = new int(78);
f(ptr);
std::cout << *ptr << std::endl;
return 0;
}
Compiling produces: [scott_s@local Code] g++ const.cpp
const.cpp: In function ‘void f(const int*)’:
const.cpp:5: error: assignment of read-only location
But if we make it: #include <iostream>
void f(const int* var)
{
int* sneaky = const_cast<int*>(var);
*sneaky = 42;
}
int main()
{
int* ptr = new int(78);
f(ptr);
std::cout << *ptr << std::endl;
return 0;
}
We get: [scott_s@local Code] g++ const.cpp
[scott_s@local Code] ./a.out
42
C++ gives us escape hatches all over the place. I think that the modern approach of compartmentalizing all escape hatches into explicitly regions of "unsafe" code, and providing no escape hatches outside of such regions, is much better.- Ownership: means no unsafely shared mutable state, checked at compile time with no runtime overhead. Also, no explicit deallocations!
- A real type system: Rust's type system is inspired by the Hindley-Milner type inference algorithm used in languages like ML and Haskell
These are the ones that come to my mind:
- ML family (Standard ML, Caml, OCaml, F#)
- Haskell
None of these are competing in the same space as Rust: fine-grained memory control (read: perf on par w/ C++) with zero-cost abstractions for safety.
You can certainly argue that all of those languages are as safe as Rust, with the lack of nulls and explicit mutability (taken to a new level in Haskel), but you can't say they expose a memory models that actually reflects the underlying system (and are as tunable) to the extent that Rust does.
Like Java generics and subtyping, any given part may be simple on its own, but the combination is not.
Rust tracks lifetimes for stack- and dynamically-allocated values as part of the type system; hence "owned" pointers. Which are horrible by themselves; hence "borrowed" pointers and the resulting lifetime complications. Rust includes traits, which are similar to Haskell type classes and are very nice. However, they come with a heapin' helping of their own complexity.
And so forth. Rust is aiming for a sweet spot somewhere between a relatively-simple Hindley-Milner[-Damas] parametric polymorphism and full-on dependent typing. So far, I think it hits a pretty nice local optimum.
*I actually think Rust is relatively simple: once one "gets" ownership, then most of the other hard things follow from it, including lifetimes.
The Rust compiler checks it for you, so you don't have to trust anyone. :)
In the past Rust has attempted to use the type system to prevent memory leaks in certain cases, but the features that attempted to do so were deemed overly restrictive to use for practical purposes. Nowadays I'm sure it's possible to leak memory if you try. Honestly I've never heard of a Turing-complete language whose type system can provide such a guarantee.
SPARK (a dialect of Ada) can, I believe. But it does so by forbidding allocation :)
Also does it enforce that memory is consistently used as a single type? Can you allocate a byte array and then cast it to an appropriately sized array of integers?
> Doesn't it make a stronger guarantee, that you cannot
> cause an invalid dereference?
I'm not knowledgeable enough to answer that question precisely.However, I can tell you that Rust's type system is not strong enough to obviate bounds checking. I hear you'd need something like Idris' dependent types for that. Rust bounds checks arrays dynamically (there are `unsafe` functions available to index an array without bounds checks), and avoids bounds checking on arithmetic by guaranteeing that fixed-sized integers will wrap on overflow (which is gross, but might be changed to fail-on-overflow if it doesn't hurt performance too much).
> Can you allocate a byte array and then cast it to an
> appropriately sized array of integers?
You can't do this in safe code, but you can in `unsafe` code via the `std::cast::transmute` function, which does still enforce that both types are the same size.That's a bummer. It seems doable, but maybe it is too complex.
> avoids bounds checking on arithmetic by guaranteeing that fixed-sized integers will wrap on overflow (which is gross, but might be changed to fail-on-overflow if it doesn't hurt performance too much).
That would be nice as a default, but I'd be afraid it would hurt performance too much for numerical code. You'd definitely want a way to express arithmetic should be allowed to overflow (ie. that omits the check).
On a related note, one thing that is sorely missing in C and C++ is a way to test whether a value will overflow when converted to a different type (I wrote a blog article about this point: http://blog.reverberate.org/2012/12/testing-for-integer-over...)
In general, eliminating runtime bounds checking is solving the halting problem.
let v = [1, 2, 3];
if halts(some_program) { v[1000] } else { v[0] }
Of course, this doesn't meant that it's impossible in a subset of cases, e.g. people are doing work on fast range analysis for LLVM, which would be able to remove some of the bounds checks sometimes: http://code.google.com/p/range-analysis/ (that analysis also applies to the arithmetic, and apparently only makes things single-digit percent slower (3% iirc).)Your example does not convince me that this follows. In cases where the compiler cannot statically prove which branch will be taken, I would expect it to require that either path can be executed without error (so in your example compilation would fail). But you could use static range propagation to allow code like this (given in C++ syntax since I'm a Rust novice):
void f(int x[], unsigned int n) {
n = min(len(x), n);
for (int i = 0; i < n; i++) {
x[i];
}
}
Maybe not the greatest example since I would hope that Rust+LLVM would hoist its internal bounds-check to emit something very much like the above anyway. I guess intuitively I just hope that there's more that can be done when it comes to static bounds-checking. window[Math.random()] = new Array(100000);
A much more interesting question, in some ways, is what guarantees you have about not accessing no-longer-alive objects. That's where Rust has some serious advantages over C++, say.Admittedly, the memory safety features of the type system haven't been formally verified, but this is a goal, and there is a rather large piece of in-source documentation: http://static.rust-lang.org/doc/master/rustc/middle/borrowck... (I haven't read it, so I have no idea if it will make any sense to someone who doesn't know Rust.)
The whole point of Rust is to make memory management safer.
The whole problem Rust is trying to solve is that a programmer can do manual memory management without anyone needing to trust that they have gotten it right. The compiler can automatically check correctness (except for unsafe blocks, which are kept to a minimum).
> And how do you propose to get a simpler*
> language with equal control and safety?
Easy - write your own compiler! :)1. Languages that people moan about
2. Languages that people don't use
I'd love if magical syntax fairy would make Rust easy to read as Python, but I think the complexity fits the domain. Safe manual memory handling is probably gonna look weird because of guarantees it must make.