As of this writing of this article D is on the verge of getting memory safety mechanisms that prevent memory escaping and use after free. Which great for D but it can surpass other languages in the C family both in terms of abstraction and also memory safety.
Walter Bright is currently in the process of developing a DIP for a borrow checker. This was spawned on by the success of Rust and its borrow checker. But unfortunately in terms of syntax this is a kind of magic syntax with different semantics being applied to a pointer even if the same syntax is being used within a function.
We I think can do better than applying different semantics to a pointer magically.
Instead of an automatic detection and determination lets introduce a new storage class called headconst
.
This is fairly simple in behavior that you may have already guessed.
It is a memory reference that cannot be altered in anyway nor implicitly casted to anything else (as to prevent free
'ing or corrupting the memory).
The effect of this new storage class is of course transitive just like const
is today. To prevent any internals being linked by pointer.
As a reference it is either a slice or a pointer. The protections are from the compiler, and are not reflected within the codegen itself.
Of course since headconst
is tied to the lifetime of the memory owner, it will rely on the already existing work of DIP25, DIP1000 and of course the work that Walter has already done for his borrow checker implementation.
I propose a head-const implementation over the borrow checker for a single but important reason. From my recollections I do not remember anyone asking for a borrow checker but countless of others asking for head-const in D. This solution has multiple use cases on top of memory safety that will satisfy even the most uncaring for memory safety to provide it.
An example of this can be found here: https://gist.github.com/rikkimax/4cb2cc8ddcac33c1a9bb20de432f9dea
This proposal adds the ability to specify types and values which are members of a template via a template parameter. It is an extension of a named arguments DIP such as Walter's and was included in my DIP 1020.
There are a few variants of syntax, but they all do the same thing. A few of these are:
alias X = X;
enum Y = Y;
@property {
typeof(auto) SomeType;
auto SomeValue;
}
and
template Foo(public SomeType, public int SomeValue) {
}
Which can be used like:
static assert(Foo!(SomeType: int, SomeValue: 0).SomeType == int);
Draft DIP pre template: https://gist.github.com/rikkimax/046fb4451e8cbac354ecb292f9a76798#file-template-parameters-as-properties-of-a-type-md
Now you may be thinking why did this get a heading and not be a step? Well it is simple, this section will be devided up into two further sections.
First is type classes, this provides a static interface which can be used to compare against other types. This is the set of features most people who know signatures (type classes, Rust trait's and Swift protocols) will expect.
The second set is the dynamic bits which give signatures their runtime representation. Programmers from C and related languages will recognize a few design decisions due to having written code like it.
We need a way to tell the compiler directly that we want to compare another type against a specification and what defines that specification.
There will always be situations where we can't do that directly so instead we provide ways to inform the compiler what those conditions are and why it did or probably did fail.
This can be done by using static assert
with its message parameter for why it failed.
These errors can be eaten by the compiler and fail any type checks being done implicitly.
Due to being eaten, they can be propergated up should the need arise in the calling code.
For type verification purposes a signature has three items in its body of interest. First is the ability previously discussed to say some property of the implementation is wrong. The second is named members settable via template parameters but is also able to be inferred from an implementation. Lastly is method declarations.
A method declaration is the same as a method declared in a interface. It has no body unless its final.
A signature is able to inherit from other signatures too. Unlike with a class system there is no problem of a diamond inheritance. Signatures work based upon less restrictive in the interface to more restrictive in the implementation. In other words a child of another signature may make the method more specialized (i.e. adding const, child class as a return type ext.). But it may not remove existing specializations.
An example of this is:
signature InputRange {
@property {
typeof(auto) ElementType;
}
ElementType front();
bool empty();
void popFront();
}
signature ForwardRange : InputRange {
ForwardRange!(ElementType: ElementType) save();
}
You can of course compare a signature instance and a signature declaration via the is
expression with awareness of happening inside another signature of the same declaration (false if true).
With :
checking for the non-template-initiated type and ==
checking the exactness (with or without template initialization) exactness.
This same method is used for comparing a signature (either the declaration or the template inititialized version) with a struct or class.
Existing template parameter syntax is extended to match existing =
and :
support as per the prior statement of is
expression.
Some type that can construct a Signature instance:
void func(auto:Signature arg)
=> void func(T)(T:Signature arg)
Verify and document return type of a function:
auto:Signature func()
=>
auto func() { ... }
static assert(is(ReturnType!func : Signature));
Signature as a function parameter without being template initialized:
void func(Signature value)
=> void func(T:Signature)(T value)
ABI:
struct SignatureInstance(ParamsExt) {
copy constructor
destructor
void* ptr; // aka this
...fields
...methods
}
In other words: void*[Size]
.
Where field is a pointer and method is a function pointer (unless there are cases where it must be a delegate and have its own context pointer). Copy constructor and destructor are treated as if they were methods that are virtual.
A signature instance can be implicitly created when passed as a function argument or as a return value.
I.e.
Signature foo(Signature) {
return ImplementationB();
}
...
foo(ImplementationA());
Within a signature this
refers to the implementation and never the signature itself.
typeof(this)
is available when inferring and the expression (working upwards) should be replaced with false. So that:
signature Foo {
static assert(!is(typeof(this)) || __traits(hasMember, typeof(this), "something"));
}
when inferring the second expression in the static assert is used and not the first.
A method that is final is described under type verification, otherwise it is virtual.
If an implementation has a method, then the signature does not need to provide a body for a method declaration. If a method body is provided and the implementation provides one, then the implementation is chosen unless override is used on the method declaration.
Making safe be the default in D is a highly desirable feature that would create breakage in the short to medium term and will likely split the community in some way.
But there is a way to do it with minimal to no code breakage.
This is done by utilizing the head-const feature described in step 1.
A function is automatically @safe
if it fulfills this rule: All pointer declarations (slices, classes, raw ext.) including parameters must be headconst
or it must have an expression assigning it via new
.
If the later of the two of the rule it must never be assigned to.
To work around this you can mark a function @system
or @trusted
to get the old behavior.
Furthermore ref
and out
parameters on a function should be rewritten as headconst
while keeping the same behavior from outside the function.
Signatures
Grammer
NOTE: EnumDeclaration definition looks off
NOTE: Parameter and StorageClass may be wrong, but it is the most convenient place to put it in the grammer right now
Compile Time
Interface vs Implementation vs Concrete Interface
FIXME: concrete interface???
This document introduces a new type called a signature. A signature is an interface that may be used for structural typing.
The format it takes is that of a polymorphic typing of inheritance where the declarations utilize type recursion to determine validity of the symbols within the body but not for member property expressions and method bodies, for that it uses type dependecy.
Type dependency within the D programming language in the context of this specification refers to the
this
reference pointing to an implementation type with any user of it being templated around it.An implementation type will be defined as either a struct or class. An implementation instance shall be defined as a pointer to a struct or a class non-null reference.
REFERENCE: William R. Cook, Walter Hill, and Peter S. Canning. 1989. Inheritance is not subtyping. In Proceedings of the 17th ACM SIGPLAN-SIGACT symposium on Principles of programming languages (POPL '90). Association for Computing Machinery, New York, NY, USA, 125–135. DOI:https://doi.org/10.1145/96709.96721
Type Checking
A signature may be used as part of type checking during compilation against an implementation type. This can be done inside an is expression, at a variable declaration, function or template parameter and at the return type of a function.
Identifier : Type
syntax.auto
keyword the compiler must detect this to template the outer symbol. For examplevoid func(auto:InputRange)
will becomevoid func(__param1)(___param1:InputRange)
. Where the name__param1
was made up for this example.auto
it may resolve to a template parameter of the given symbol or another type.Type
to check against if it is a template instance of a signature then the checks are further extended to include checking if the signature is not only the signature itself but also of that specific template instance.is
expression is similar to a type checked variable declaration, parameter or return type except it accepts aType
rather than anIdentifier
. Allowing for more complex operations including CTFE.Opaque Signature
An opaque signature is hereafter defined as any given declaration, parameter or return type known at the point of a scope as either being able to become an instance of the given signature.
Identifier : Type
syntax.Member Parameter
A member parameter is a template parameter that is declared within the body of a signature. It is exposed as a member but will be set by template initialization.
alias
orenum
declaration that has a preceeding@property
attribute. These are to be appended to the end of the template parameter sequence during parsing and must remain exposed as a member during symbol lookup.This Context
Within the scope of a signature body, having access to the implementation is required to properly infer more complex symbols. Hereafter this within a signature body shall be defined as refering to a reference to the implementation.
this
keyword.typeof(this)
and these shall be treated as the default values during inferation if the implementation type does not have the member properties name as a member.this
as to be an opaque signature to the implementation instance. Allowing for complex decisions, fallbacks and behaviors.Slice Boxing
Signatures can only have implementation types of type struct or class, slices would enable input ranges to continue to work while migrating to a signature based design.
Explicit wrapping is an option, but a simpler alternative that the user would not need to be aware of is an opaque boxing of slices.
SliceBox
is added, that must be provided a struct that must have:Run Time
Creating An Instance
ABI