Rust combines concepts from C, C++, Haskell, and other languages. Each value is either a primitive value or a structure, and each value has a type. The compiler uses the type of each value and function to ensure that a program is type-safe, and to compile it to performant machine code. A type annotation is required where the compiler can not infer a type of a value, variable, parameter, or result. Code is organized into packages which are composed of crates, which themselves are composed of modules. A module may contain any of the various programming artifacts Rust provides, namely variables, functions, traits, and trait implementations.
Rust has various features for programming the structure of the program itself, i.e. for metaprogramming. A templating system provides the ability to write generic structs, functions, traits, and trait implementations, which the compiler then uses to produce specific implementations as needed. One declarative and three procedural macro systems are also included. One procedural macro is for deriving code for structs and enums, another is for defining custom attributes, and the last is for function-like macros that process tokens during compilation. Rust also features closures, pattern matching on language items, similar to that of Haskell, for such purposes as destructuring variable assignment and code branching according to data type.
The compiler tracks the owner of each value so that it knows its lifetime and can arrange for its storage to be freed at the right time, relieving the developer of the burden of deciding when to free resources, and opening up new avenues for optimization via copy-free data operations. When a value is passed as an argument to a function, its ownership also passes to that function, and to prevent aliasing, the value is no-longer accessible to the caller. The caller of a function owns the value returned by the function. A value may be passed by reference, in which case the caller continues to own the data, and the called function owns the reference.
A variable may represent a value or a reference to a value , and is immutable unless annotated as mutable. Multiple references may exist, but only one mutable reference is allowed. & annotates a reference, and mut annotates a mutable variable. Rust tracks all references and ensures that no reference has a lifetime greater than the lifetime of the variable to which it refers. Because the compiler only inspects function signatures to determine lifetimes, sometimes it can not infer the lifetime of a reference, even when it is obvious from the body of the function. If the lifetime can not be inferred, it must be annotated just prior to the type. ` annotates a lifetime.
Like Tcl, Rust does not employ a garbage collector, but it also does not normally employ reference counting. Instead, the resources for each value are deallocated as soon as the block of code that owns it is finished with it.
The type of a value may a primitive type, or the type of a defined tuple, an enum, or struct. A tuple is a finite heterogeneous sequence. A struct may contain either named or unnamed fields. Each field is either a primitive value or a reference to primitive value, struct, enum or function. An enum is a type that enumerates a set of possible types, i.e. it is known as a union in C, but can also function as a set of uniquely-named but otherwise undefined types, just like an enum in C. match is used to provide a way to handle each of the possible types of an enum, and must handle all of them, guaranteeing full coverage.
A trait specifies a set of functions that operate on a data type, and serves as as an interface to that data type. A function defined as part of a trait is called a method, and its first argument is a reference to a value having that type. An arbitrary number of traits may be defined for each type.
impl is used to provide a set of function definitions that implement a particular trait for a particular type of struct. To maintain coherence, Either the trait or the struct must be defined within the same crate.
If the copy trait is implemented for a type, then a value of that type may be trivially copied, i.e. it does not involve any memory allocation from the heap. When such a variable is assigned to another variable, a copy is made and both variables remain valid. When a variable that doesn't have the "copy" trait is assigned to another, the compiler avoids aliasing by making the variable holding the original invalid.
If the clone trait is implemented for a type, then a value of that type may be cloned: A new independent value is produced by copying the underlying data.
Both variables and references are immutable by default. mut declares mutable variable, and &mut declares a mutable reference. To prevent data races, no other references are allowed while a mutable reference exists.
Rather than specifying the type of a value, it is also possible to specify one or more traits that must be defined for the value. which allows duck typing at compile time. A trait object provides a function table that may be modified at runtime, which allows duck typing at runtime.
Rust is actually segmented into two different languages: safe Rust and unsafe Rust. In the safe parts, nothing is undefined, nothing is null, only safe type conversions are possible, and only safe memory operations are allowed. There is, however, an escape hatch: A function annotated as unsafe takes upon itself the responsibility to guarantee the same things that the Rust compiler guarantees, so the compiler makes no further effort to check that function for conformance. This makes it possible to write such a function in another language such as C, if needed.
One of the design goals of Rust is for the compiler to be able, as much as possible, to infer the intent of the programmer. The compiler also takes a more active role than most compilers in making suggestions for fixing problems that it finds. This helps to smooth the learning curve.
Both Tcl and Rust provide automatic memory management without using a garbage collector, and both are memory-safe. Where Tcl uses reference counting and manages memory dynamically at runtime, Rust uses its borrow rules to constrain the use of references, and tracks all memory accessors at compile time to ensure that they are cleaned up is soon as the the block of code that owns it is no longer in use. Tcl's internal reference counting relies on the developer to properly increment and decrement references, which is error-prone since proper reference counting can not be checked for correctness. Because Rust would automate the task of reference counting, relieving the developers of Tcl from this burden, it may prove to be an ideal implementation language for Tcl.
Rust does not provide traditional object-oriented programming facilities. Inheritance has proven to be a liability, so Rust doesn't have it. Instead, the emphasis is on structures, and the interfaces that those structures support. Each structure has a type, and where inheritance might be used in other languages, composition is used instead: It is common instead to create a new type of structure that contains another structure, and whose interfaces override the interfaces of the contained structure. This design allows for zero-cost abstractions, since all the details of function dispatch are worked out at compile time. Where needed, Rust also provides facilities that provide for runtime dispatch.
Both languages are founded on the concept of immutable value, but Tcl values are much more abstract, both semantically and technically. In Tcl, copy-on-write can be implemented underneath the surface of the language, whereas in Rust, a programmer details directly with such issues. This level of abstraction is the most fundamental difference between the languages.
As value-oriented languages, both languages have no concept of null, which is anything but a value.
Both languages are memory-safe, assuming that the underlying implementation/compiler is free of bugs. Rust is type-safe, both in the sense of memory layout and data structure, and also in the sense of the semantics of the data and the operations on it, as only the intended structure is accepted as the argument of a function or the value of a variable. while Tcl is not type-safe, it is also not type-unsafe: Each procedure may choose to implement the necessary runtime type verification. However, even then a procedure has no way to determine which program component the declared the data type, and therefore can never achieve the level of whole-program semantic type safety that Rust does.
For a program written in Rust, the additional burden of annotating data types and tracking data mutability and ownership is high compared to practice of taking things at face value in Tcl, and all the type annotation, templating, and boilerplate code makes a program written in Rust far more verbose, adding to the up-front development cost. Tcl is far more concise than Rust, but also far less performant. In addition to performance, it is a big win for Rust that all the additional type information a programmer provides in the source code allows the compiler to verify some aspects of its correctness, and probably reduces the size of any accompanying test suite by at least the same amount of code and effort. If a program compiles successfully, it is already in no small part semantically verified by the compiler. An equivalent program in Tcl would have to ensure the same level of test coverage via a series of unit tests, and even then is not testing semantic correctness so directly.
"When you use Rust, it is sometimes outright preposterous how much knowledge of language, and how much of programming ingenuity and curiosity you need in order to accomplish the most trivial things. When you feel particularly desperate, you go to rust/issues and search for a solution for your problem. Suddenly, you find an issue with an explanation that it is theoretically impossible to design your API in this way, owing to some subtle language bug. The issue is Open and dated Apr 5, 2017." [L1 ]
dkf - 2022-06-15 14:30:02
I suspect that Rust will not prove to be as ideal a language to implement Tcl as some think. There are a number of concerning aspects, but the limitations on traits and lifetimes would seem to be the main ones (along with the general hostility to dynamic linking); it's not at all clear how one might have third party commands implemented (where those need the equivalent of ClientData) and the lifetime management is almost certainly going end up forcing a lot more copies of things being used, which is known to be expensive.
The final problem, the one that really dissuades me, is the sheer cost in developer effort of moving a codebase the size of Tcl and Tk.