February 05, 2014
On Wednesday, 5 February 2014 at 00:47:27 UTC, Walter Bright wrote:
> The difficulty comes when needing to transfer the nullability of the argument to the nullability of the return type. Implicit conversions don't help with that.
>
> See inout.

You could always do it with a template; T identity(T t) { return t;} will literally work. (I'm pretty sure it would also work with const and immutable; the reason inout exists isn't that it is impossible, it is just to counter template bloat)

Is this common enough to warrant an addition, or would templates be good enough when it is needed? inout has a very common real world use case: slicing.

inout(char)* strchr(inout(char)* haystack, in char* needle);

What's the real world equivalent to strchr with regard to nullability?


* typeof(this) inside a class is never nullable. You can't call a method with a null this. So a method that returns this can always just return the regular (not null) type.

* this[x..y] is never nullable. Slicing a null reference is illegal, so again, no need to offer the nullable overload.

* Returning a field of a nullable type is illegal, dereferencing null to get to the fields is illegal. Whereas a struct might return inout(member_variable), it never makes sense to do inout_nullability(member_variable).


I can't think of a single real world case where you'd need this... and if I'm wrong, we can always use a template and/or revisit the problem later.
February 05, 2014
On Wednesday, 5 February 2014 at 00:49:25 UTC, Walter Bright wrote:
> What happens with this:
>
>     T identity(T t) { return t; }

My previous post was with regard to nullability. This one is about ownership.

The answer is the same: if you need this, use a template, otherwise, you always deal with a specific type (if you are allocating a new copy or need to store the reference somewhere outside the owner's scope) or a borrowed type:

GC!Object obj;
static assert(typeof(identity(obj) == GC!Object);

and so on, you can test this today, D already has all these options.


If you don't have a template, how do you approach this? Well, this is really nothing new for one:

class Foo {}

Foo foo = new Foo();
Object obj = foo; // legal

Object identity(Object obj) { return obj; }

foo = identity(foo); // won't compile without an explicit cast



When you work with interfaces, some information is lost. That's a pain sometimes, but it is a feature too - the function that works on the interface doesn't need to care about anything else.

With class inheritance, we can address this with covariant overrides.... but only of it is a member function (method).



So let's just accept that the free function loses data. You can't get a container back from a range in the general case.


So, what about the method:

class Foo {
   typeof(this) identity() { return this; }
}


What is typeof(this)? I'll tell you: Foo! Here's why:

*) The internal this pointer is never null, and thus never needs to be nullable, so that's out of consideration (see my last message)

*) The internal this pointer does *not* own its memory. Calling "delete this;" is (I think) undefined behavior in D: the class might exist on the stack, or be allocated with malloc, and calling freeing the memory inside a method is likely to lead to a crash when the invariant is called upon returning anyway!


Since the object does not own its own memory, it must always assume this is a borrowed reference. Therefore:

* Escaping `this` is prohibited.

Foo globalFoo;
class Foo {
   // illegal, escapes a reference to a borrowed reference
   void storeThis() { globalFoo = this; }
}

Consider that this is wrong if the class is made with std.conv.emplace (or the deprecated scope storage class) on a stack buffer.

This is silently wrong today, it can leave a dangling reference. (though since emplace is not @safe, we do have that protection against such problems... though since we're talking about avoiding the GC here, emplace is something we probably want anyway)


* Returning this is always a borrowed pointer, unless you explicitly make a copy.

class Foo {
   Foo identity() { return this; }
}

GC!Foo foo = new Foo(); // owned by the Gc

Foo foo2 = foo.identity(); // borrowed reference, NOT GC!Foo


Therefore, we cannot escape it to a global that way either. If we want that, we have to work with foo directly, bypassing foo.identity.

A clone method would create a new owned thing, so it returns something more specific:

class Foo {
   GC!Foo clone() { auto foo = new GC; /* copy methods */ return foo; }
}


A clone method might also be templated on an allocator, and can thus change the return type as needed by the allocator. This wouldn't be virtual... but it kinda has to be.

class Foo {
    virtual GC!Foo clone() {...}
}
class Bar : Foo {
    override RC!Foo clone() {...}
}

You wouldn't want that anyway, since if you called it through the foo interface, you wouldn't know how to free it (does it need to run a struct dtor? The GC? The caller needs to know.)


Gotta pick an allocation method in the base class if you want it to work as a virtual function. So... yeah do that or use a non-virtual template. You can make it work.

A clone method typically wouldn't even be inout anyway so meh.

* A class is responsible for its member references but not itself. When the class' dtor is called, it needs to call member struct dtors, but does not call free(this) - leave that to the outside allcoator. This is already reality today (the GC calls the dtor before releasing the memory).
February 05, 2014
On 2/4/14, 5:34 PM, Frank Bauer wrote:
> On Wednesday, 5 February 2014 at 00:50:55 UTC, Walter Bright wrote:
>> Again, what happens with:
>>
>>     T identity(T t) { return t; }
>>
>> ? I.e. the transference of the argument pointer type to the return type?
>
> As far as I see, in Rust the pointer type is lost on the return value as
> well, if your function takes a reference (borrowed pointer). But you can
> do:
>
> fn identity(x: &T) -> T {
>      return *T;
> }
>
> fn main() {
>      let a = ~T;
>      let r = ~identity(a);
> }
>
> That is: pass by reference and return a value type.

Looks like a major weakness.

Andrei

February 05, 2014
On Wednesday, 5 February 2014 at 04:22:21 UTC, Andrei Alexandrescu wrote:
> Looks like a major weakness.

Even assuming that's true (something I don't agree with), it doesn't apply to D anyway since we can always template these functions!


I'd like to say again that multiple pointer types is something we already have in D:

* unique pointer (I think this is ~ in Rust but rust syntax looks like vomit to me) is achieved in D by disabling postblit with a struct and freeing in the dtor (RAII). It CAN offer a release method to transfer ownership.

* ref counted pointer is achieved with postblit adding ref, dtor releasing ref.

* GC pointer could be as simple as struct GC(T) { @trustme T t; alias t this; } (where @trustme disables the escape analysis checks I'd like to see implemented. Alternatively, if escape analysis was built in, we would make t private than then offer a getter property that returns scope T.)

* borrowed pointer is the regular references we have today, plus escape analysis.


We don't have to speculate how these work. Just write functions that use GC!T or RC!T or Unique!T - types we can go ahead and write immediately, they are pretty basic structs. Phobos even has unique and refcounted right now. We're already halfway there!
February 05, 2014
> A function that takes a borrowed pointer &T can also be called with an owning pointer ~T, an RC pointer Rc<T>, or a GC pointer Gc<T>. They all convert neatly to a &T. One function to rule them ... err .. accomodate all.
No. That would be nice to have, but it's actually not the case. The only type that automatically coerces to a borrowed reference is an owned pointer. For value references you fist have to create a borrowes ref through
&val. For Gc and Rc you have to pass something like ptr.borrow().borrow_mut ().borrow()
February 05, 2014
>> Again, what happens with:
>>
>>    T identity(T t) { return t; }
>>
>> ? I.e. the transference of the argument pointer type to the return type?
>
> As far as I see, in Rust the pointer type is lost on the return value as well, if your function takes a reference (borrowed pointer). But you can do:
>
> fn identity(x: &T) -> T {
>     return *T;
> }
>
> fn main() {
>     let a = ~T;
>     let r = ~identity(a);
> }
>
> That is: pass by reference and return a value type. If you then pass an owned pointer ~T, you can directly construct the returned value in heap allocated memory ~identity(a) and assign the resulting owned pointer. The compiler is smart enough to not do unnecessary copies.

This would definetly make r an unbecessary heap copy.
Return *x would also only work for POD values, because for all others it would cause a (not allowed) move ibstead of a copy. For a copy you would have to return x.clone().

You can return by reference in rust, but you have to annotate everything with proper lifetimes.
Possibly incorrect example:
fn identity<'a>(x: &'a T) -> &'a {
x
}

I dont think the comparisons to rust make too much sense because the whole system works completely different. For D the question mainly is how to manage classes (and also interfaces to them) with GC or ARC. Rust at the moment can't manage any interfaces/traits with theit smart pointers.
February 05, 2014
On Tuesday, 4 February 2014 at 02:59:11 UTC, Andrei Alexandrescu wrote:
> On 2/3/14, 5:51 PM, Manu wrote:
>> I'd have trouble disagreeing more; Android is the essence of why Java
>> should never be used for user-facing applications.
>> Android is jerky and jittery, has random pauses and lockups all the
>> time, and games on android always jitter and drop frames. Most high-end
>> games on android now are written in C++ as a means to mitigate that
>> problem, but then you're back writing C++. Yay!
>> iOS is silky smooth by comparison to Android.
>
> Kinda difficult to explain the market success of Android.
>
> Andrei

Are you agreeing or disagreeing with his point?
I don't know about other Android phones, but on my Galaxy S3 what he says is true and just the other day I had to reboot my phone as it was very, very slow, the reboot made it "fast" again..

I don't like Apple (nor Microsoft), so I'm stuck with Android, but this doesn't mean that I consider that Android is good.. For me it's like Windows in the early days, not very good but usable.

renoX
February 05, 2014
On Wednesday, 5 February 2014 at 00:47:27 UTC, Walter Bright wrote:
> On 2/4/2014 2:39 PM, Dicebot wrote:
>> Also non-nullable types should be implicitly cast to nullable parameters so you
>> don't always need to support all cases distinctively.
>
> The difficulty comes when needing to transfer the nullability of the argument to the nullability of the return type. Implicit conversions don't help with that.
>
> See inout.

Why can't inout be used for that as well? Same for pure & co.

In my opinion "inout" should be simply generic placeholder saying "allow only code that works for all possible qualifiers and preserve those in return type".

But nature of nullable is such that you rarely want to transfer it. Is can be useful as return type sometimes but when you pass it around and process it you almost always want it to be non-nullable. There is no practical benefit in trying to provide all-allowing API's.
February 05, 2014
On Wednesday, 5 February 2014 at 15:56:44 UTC, Dicebot wrote:
> On Wednesday, 5 February 2014 at 00:47:27 UTC, Walter Bright wrote:
>> On 2/4/2014 2:39 PM, Dicebot wrote:
>>> Also non-nullable types should be implicitly cast to nullable parameters so you
>>> don't always need to support all cases distinctively.
>>
>> The difficulty comes when needing to transfer the nullability of the argument to the nullability of the return type. Implicit conversions don't help with that.
>>
>> See inout.
>
> Why can't inout be used for that as well? Same for pure & co.
>
> In my opinion "inout" should be simply generic placeholder saying "allow only code that works for all possible qualifiers and preserve those in return type".
>
> But nature of nullable is such that you rarely want to transfer it. Is can be useful as return type sometimes but when you pass it around and process it you almost always want it to be non-nullable. There is no practical benefit in trying to provide all-allowing API's.

If null becomes an invalid value for object references and pointers, then we can use it to unambiguously mean "item does not exist", and it becomes Just Another Value, so it is useful to transfer in various circumstances.
February 05, 2014
On Wednesday, 5 February 2014 at 17:09:31 UTC, Meta wrote:
> If null becomes an invalid value for object references and pointers, then we can use it to unambiguously mean "item does not exist", and it becomes Just Another Value, so it is useful to transfer in various circumstances.

Yes, in that cases you transfer it as Nullable!T which is supposed to be single generic way to pass both value and reference types that can possibly be undefined.

Judging by typical code I see though, such "does not exist" case is most commonly processed as

if (!param)
    return; // return null;

So if `param` becomes non-nullable you simply don't call processing function for null cases.