Draco-lang / Language-suggestions

Collecting ideas for a new .NET language that could replace C#
75 stars 5 forks source link

Low-level/unsafe features? #120

Open KirillAldashkin opened 1 year ago

KirillAldashkin commented 1 year ago

1) C# is known to have a quite good combination of low-level performance features and a high-level OOP model 2) Writing the Draco's core library will require some features that will be unsuitable for regular code

This will be a discussion of including (or not) perfomance-related, low-level and unsafe C# features such as: 1) structs 2) unsafe context in general 3) Data pointers (*T) and refs 4) Function pointers (delegate*<>) 5) Fixed buffers 6) stackalloc

KirillAldashkin commented 1 year ago

Just my thoughts: 1) struct is definitely a must-have feature, but all boxing casts (to object/interface) should be done explicilty (#68) 2) unsafe block is redundant, unsafe modifier for methods/types is ok 3) C-style pointer manipulation syntax is awkward (* and & are used in arifmetic so some keywords instead will be better). Something like struct Ptr<T> should be introduced in core library to be used as a pointer type in Draco. This will unite pointers with rest of type system (in C# pointers are not object). No ideas about refs since I don't know their internal implementation very well, but something like ref struct Ref<T> to wrap them into a complete type is wanted too (but there's a problem) 4) Some analog of struct Ptr<T> from above is also wanted to unite function pointers with rest of type system, but there is a problem too 5) Good as is, but should use Span<T> instead of a pointer and not be unsafe 6) Good as is, but should return Span<T> by default

Binto86 commented 1 year ago

So im going to comment only on the points i know something about:

WhiteBlackGoose commented 1 year ago

I vote for annotation with attribute for structs.

Something like struct Ptr should be introduced in core library to be used as a pointer type in Draco. This will unite pointers with rest of type system (in C# pointers are not object).

To consider: that's similar to how F# does it, but it may be worth a look

but something like ref struct Ref is wanted too

I say a type annotation as well, like [Struct] and [RefStruct] (could also go with [StackOnlyStruct]).

LPeter1997 commented 1 year ago

Thanks for your input! I generally agree with your points - and admittedly, haven't really thought too deeply on some of them. I definitely like the "leaving the C-heritage syntax behind" mentality,

KirillAldashkin commented 1 year ago

but something like ref struct Ref is wanted too

I say a type annotation as well, like [Struct] and [RefStruct] (could also go with [StackOnlyStruct]).

Didn't talk about ref struct definition syntax here. I meant to introduce the type Ref<T> for wrapping ref T in the same way as Ptr<T> wraps T*. But, as I said earlier, there is a problem with this.

KirillAldashkin commented 1 year ago

How about introducing ref lambdas?

Idea

ref func(a: int32) = a * a

is a ref lambda which type is:

unsafe ref struct RefFunc<TArg, TRet> // some kind of "Func<TArg, TRet>" delegate type
{
    private void* _context;
    private delegate* managed<void*, TArg, TRet> _function;

    public TRet Call(TArg arg) => _function(_context, arg);

    public RefFunc(void* context, delegate* managed<void*, TArg, TRet> function)
    {
        _context = context;
        _function = function;
    }
}

If ref lambda captures some context, then it have the same ref safety rules as scoped ref, otherwise as unscoped ref

Example

func foo(bar: ref (int32) -> unit): unit {
    bar(1)
    bar(2)
}
func main(): unit {
    var counter: int32 = 1
    var baz = ref func(add: int32): unit { counter += add; }
    foo(baz)
    Console.WriteLine(counter)
}

compiles to

static unit foo(RefFunc<int, unit> bar)
{
    bar.Call(1);
    bar.Call(2);
    return unit();
}

[CompilerGenerated]
ref struct _Context
{
    public int counter;
}

[CompilerGenerated]
unsafe static unit _lambda(void* context, int add)
{
    ((_Context*)context)->counter += add;
    return unit();
}

unsafe static unit main()
{
    _Context context = new() { counter = 1 };
    RefFunc<int, unit> baz = new(&context, &_lambda);
    foo(baz);
    Console.WriteLine(context.counter);
    return unit();
}

[CompilerGenerated]
static void _true_main() => main(); // method that returns neither "void" nor "int" can't be an entrypoint

Pros

1) ref lambdas are fast and allocation-free at instantiating. 2) Capturing context in ref lambdas is also allocation-free, since they can safely access the stack of the method in which they are declared.

Cons

1) ref lambdas can't go on heap, so they can't be used in async code 2) ref lambdas with context can't go outside of the method they are declared in