Some of the comments on this interesting blog post http://sebastiansylvan.com/2013/05/25/language-design-deal-breakers make me think that people only familiar with languages that allow null pointer dereference do not fully appreciate that it is an artifact of language design. I want to go into some detail about it. I'll introduce a typical language with NULL dereference, show how to transform it to a language without, and then show how proper use of the new language really does completely eliminate the problem.
Start with a pointer language, like C++, but without pointer arithmetic. And let's assume garbage collection, to eliminate explicitly freeing memory. Really, the language is more like C# or Java or Go, but let's stick with C++ syntax. I'm going to write NULL (not 0 or nullptr or nihilateration) for the null pointer.
Here are a couple of declarations in this language.
T t1; // t1 is a value of type T
T* p1; // p1 is a pointer to a value of type T
T can be whatever: int, float, another pointer, a struct, etc. But for talking about NULL, the type doesn't matter, so we'll just stick with T.
There are 3 ways to create a pointer to a value of type T.
Most simply, you can take the address of a value of type T:
&t1 // is a pointer value pointing to t1
p1 = &t1; // stores that pointer value in the pointer variable
That's easy, but not really that common in C++; and it's forbidden in Java. It's probably more common in Go.
The second way to create a pointer is by allocating a new chunk of memory:
new T() // allocates enough memory for a T value, initializes the value and returns a pointer to the memory
p1 = new T(); // stores that pointer in the variable
The third and final way to create a pointer is just to write NULL (possibly with a cast), which creates a value of type "pointer to T" that doesn't really point to anything. So, kind of misleading.
NULL // the null pointer
p1 = NULL; // make the variable p1 not point to anything
Many languages implicitly set pointers that are not explicitly initialized to NULL. In C and C++, that is true in some contexts; in other contexts the pointer just contains random garbage. Let's assume that pointers not explicitly initialized are implicitly initialized to NULL. So these are the same:
T* p1;
T* p1 = NULL;
The first two ways of creating a pointer value make it actually point to something; only the third way does not. The only way to get a NULL value is to specify NULL (or imply it by not specifying anything).
Taking the address of a value in memory always succeeds; and the NULL value is just another literal. new() can fail, either to allocate memory or to properly initialize the new value; if it fails it will throw; but if it gets past that, it can always successfully return the pointer value. In C, malloc will return NULL if it fails, but you should always check for that and usually fail with an "out of memory" error.
Let me briefly mention that in C which allows pretty flexible pointer arithmetic, such arithmetic still cannot turn a non-NULL pointer into a NULL, at least not in a way that is well defined.
So, what can you do with a pointer once you have it? In the absence of pointer arithmetic, not too much. A pointer is a value, so one thing you can do is copy it around: you can store it in variables, pass it to functions, return it from functions.
T* p2 = p1; // copy the value of the pointer p1 into p2 so that both now point to whatever p1 pointed to before
T* p2 = f(p1);
The second thing you can do with pointers is compare them:
if (p1 == p2) { ... }
if (NULL != p1) { ... }
In the absence of pointer arithmetic, less than (p1 < p2) and its brethren are forbidden.
And the last thing you can do with pointers is dereference them:
T t1 = *p1; // read through p1
*p2 = t1; // write through p2
That's it, three operations on pointer values: copying, comparing and dereferencing.
This is a pretty typical language. C#, Java, Go, C and C++ all work basically this way.
In languages like this, whether a pointer is NULL doesn't matter when you're copying it. Copying and comparing can never fail. But dereferencing a NULL pointer causes a NULL pointer exception, which makes everyone involved very sad. So you avoid NULL dereferences by judiciously sprinkling your code with lots of comparisons against NULL.
But wait, there's a better way. Let's morph the language into a new and improved language. In this new language, you can declare pointers that cannot point to NULL:
T*! v1 = &t1; // exclamation point (!) means never NULL
T*! v2 = NULL; // BZZT! The compiler won't allow this.
T*! v3; // BZZT! This is also forbidden, because it implicitly initializes to NULL.
T*! v4 = new T(); // fine; if it doesn't throw, it returns a non-NULL value
But you can still declare a pointer which can hold a NULL or an actual pointer.
T*? n1 = &t1; // question mark (?) means sometimes NULL
T*? n2 = NULL;
T*? n3; // implicitly NULL
T*? n4 = n1;
So there are two types of pointers to T: never NULL (T*!) and sometimes NULL (T*?). The new language does not support the traditional (T*) pointers from the old language.
Neither of these types (T*! and T*?) alone supports all of the operations allowed to T* pointers. T*! as we saw already, forbids assigning NULL. T*? forbids dereference, which prevents NULL derereference. You can implicitly turn a T*! pointer into a T*? pointer, but not the other way:
T*! v1 = n1; // BZZT! Cannot assign nullable pointer to non-nullable one.
T*! v1 = (T*!) n1; // BZZT! Not even explicitly.
if (n1 == v1) { ...} // same as: if (n1 == (T*?) v1) { ... }
If T*? pointers don't allow dereference and cannot be turned into T*! pointers, what good are they? Well, there is a way to get the T*! pointer back out, but it involves new syntax, the perhaps/butifnot statement:
perhaps (T*! v1 = n1) { // can assign T*? to T*! only in a "perhaps" header
// here v1 is in scope and can be dereferenced
t1 = *v1;
} butifnot {
// here it is not because n1 is NULL
t1 = t2;
}
This is sort of like
if (NULL != p1) {
t1 = *p1;
} else {
t1 = t2;
}
except that you can't accidentally forget the NULL check. Note that inside the perhaps clause you still can't dereference n1; you can dererence v1, the non-NULL pointer that n1 held.
People who have never used a language like this may initially think that it doesn't really add much value. It forces you to do your NULL checks, but that seems more annoying than anything else . Sure, there are technically no NULL dereferences, but don't you end up in "butifnot" blocks all the time with nothing to do but throw an exception, which might as well be a NullDereferenceException?
The answer to that question is: no, you never really end up in a "butifnot" block with nothing to do. Think about how you would get to this point:
perhaps (T*! v1 = n1) {
... *v1 ...
} butifnot {
// What should I do? How did I get here?
}
Think in particular about where n1 came from. Maybe you're writing a function, and it's a parameter:
void f(T*? n1) {
// ...
perhaps (T*! v1 = n1) {
... *v1 ...
} butifnot {
// How did I get here?
}
// ...
}
How did you get there? You declared the wrong type for the function parameter. You started writing the function thinking it would be ok for the pointer to be NULL, but you've now realized that you were wrong. The correct way to proceed is not to throw a NullDereference exception, or any exception. The right thing to do is to fix the declaration of the parameter. You need the parameter not to be NULL, so just declare it as non-nullable:
void f(T*! v1) {
// ...
... *v1 ...
// ...
}
No problem, no NULL dereference, no perhaps statement, no NULL checks. Sure, someone is calling your function, but if they are passing it NULL, it's just going to throw anyway, so make them fix the problem the same way.
Another way you might have gotten there is that you got the n1 is as the result of a function call:
T*? n1 = g();
// ...
perhaps (T*! v1 = n1) {
... *v1 ...
} butifnot {
// How did I get here?
}
// ...
But you need to call g() and get a non-NULL pointer back. You have two options. The better one is fixing g() to return a non-NULL pointer, but that's not always feasible. But you can always wrap it:
T*! wg() {
T*? n = g();
perhaps (T*! v = n) {
return v;
} butifnot {
throw GFailed;
}
}
And then you call your wrapper:
T*! v1 = wg();
// ...
... *v1 ...
// ...
Sure, this still may throws an exception, but it's really not equivalent to a NULL dereference. First, it fails earlier, which is better. Second, it tells you that g() didn't do what you were expecting, which is likely to be far more useful than a NULL dereference error.
By the way, a language like this doesn't need traditional NULL checks:
T*! vl = &t1;
if (NULL == v1) { ... } // known false at compile time, just from the type of v1
T*? n1;
if (NULL != n1) { ... } // ok, but not as useful as a perhaps/butifnot statement
The other thing you don't understand when you're used to languages with only nullable pointers is how rarely you actually need them. Most of the time non-nullable pointers are what you want. Which really means that the case of the function g() returning a nullable pointer doesn't happen very often.
I will post sometime about sum types and matching, which is a great language feature that lets you write T*? and perhaps/butifnot yourself, along with many other useful things.
Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts
Tuesday, June 4, 2013
Monday, May 20, 2013
RIFL vs. using
In the last several posts, I've introduced a resource management pattern I call RIFL, or Resource Is Function-Local. I've given examples in Go, Perl, and C++, and compared to RAII. Here, I'll give examples in C#, and discuss how it relates to the IDisposable pattern in C#.
In C#, a resource that needs management signals that to the coder by implementing the interface IDisposable. That contains a single function, void Dispose(), which is about as simple as interfaces get. Implementing IDisposable means that you should ensure that Dispose() gets called when you're done with the resource. And the language provides the "using" statement, which is some lovely syntax making calling Dispose() easy. A typical usage looks like this:
using (var resource = new Resource()) {
resource.Use();
} // implicitly calls resource.Dispose()
This is roughly equivalent to
var resource = new Reource();
try {
resource.Use();
} finally {
resource.Dispose();
}
The differences are mostly too subtle to affect our discussion here.
Often, as with RAII, you want some resource to have the same lifetime as an instance of a class. In that case, you construct the IDisposable resource in the constructor for the new class, and you have that class also implement IDisposable and call Dispose() on the resource in its own Dispose() method. Actually, there are subtle, confusing and dangerous interactions between Dispose(), the actual destructor (well, finalizer), the garbage collector and OS resources, which make correctly implementing Dispose() rather tricky. Fortunately, for our purposes, all of these issues are too subtle to affect our discussion much.
I'm not comfortable really talking much about Java, as I'm much more familiar with C#, but recent versions of Java have an AutoCloseable interface and an extension to the "try" syntax which is similar to Disposable and "using".
C# has similar flexibility to C++ because Disposable is so similar to RAII. Here's a few examples of using resources and non-resources in C#:
public void ExampleUsingResource() {
using (var resource = new Resource() {
resource.Use();
}
}
public Resource ExampleUseOfEscapingResource() {
var resource = new Resource();
resource.Use();
return resource;
}
public void ExampleUseOfLeakingResource() {
var resource = new Resource();
resource.Use();
} // failed to release resource properly by calling Dispose()
public void ExampleUseOfNonResource() {
var nonResource = new NonResource();
nonResource.Use();
} // no need to release nonResource, since it does not implement IDisposable
So this is even trickier than in C++, since leaking a resource not only looks like a cross between "using" a resource and deliberately allowing a resource to escape the current scope; but it looks identical to using a non-resource. And here's where it gets really tricky: the difference between a non-resource and a resource is that a resource implements IDisposable. And how do you know that when you're coding? Well, if you're using Visual Studio with IntelliSense (or something roughly equivalent), usually you would try typing "resource.Disp" and seeing if the system wants to autocomplete it to Dispose. This almost works, except for three issues.
First, and pretty minor, is that it's possible to implement Dispose without implementing IDisposable, and then you can't use "using". But you find that out as soon as you try to use a "using" statement, so that's mostly ok.
Second, and pretty tricky, is that some classes explicitly implement IDisposable.Dispose, so that the Dispose method isn't available unless you cast to (IDisposable). That means it won't autocomplete so you can be fooled into not using "using".
Third, sometimes it is apparently safe not to call Dispose on certain IDisposable objects, such as Task. This turns out to be pretty convenient, since in many of the ways you are encouraged to use Tasks in recent C# versions, it's very hard to find the right way to Dispose them. But this means that sometimes the Leaking code doesn't really leak, which is pretty confusing.
Oh, and did I mention that sometimes Dispose throws?
What it adds up to is that the IDisposable pattern seems fine, but ends up being really painful to use safely. Fortunately, C# has pretty good lambda support, so you can easily implement and use RIFL.
public class RiflResource {
public static void WithResource(Action<RiflResource> useFunc) {
using (var raw = RawResource.Obtain()) { // often: new RawResource()
var rifl = new RiflResource(raw);
useFunc(rifl);
} // implicitly Dispose (Release)
}
public void Use() { raw_.Use(); }
private readonly RawResource raw_;
private RiflResource(RawResource raw) { raw_ = raw; }
}
And it's easy to use
public void ExampleRiflUsage() {
RiflResource.WithResource(resource => {
resource.Use();
});
}
In fact, of the languages I've used in examples, C# has the best syntax for anonymous functions, which makes this the cleanest usage. And I intentionally wrote my example so that it's obvious how to add multiple calls to Use; this simple case has an even briefer alternate syntax:
public void ExampleRiflUsage() {
RiflResource.WithResource(resource => resource.Use());
}
or even
public void ExampleRiflUsage() {
RiflResource.WithResource(Use);
}
but that only works if you want to call Use() alone once with no arguments.
I am really happy to wrap up my posts on RIFL. So far, I've only blogged on 2 topics, and each of those turned into multi-post discussions, where each post was too long. I hope to find a reasonable-length topic for the next post.
In C#, a resource that needs management signals that to the coder by implementing the interface IDisposable. That contains a single function, void Dispose(), which is about as simple as interfaces get. Implementing IDisposable means that you should ensure that Dispose() gets called when you're done with the resource. And the language provides the "using" statement, which is some lovely syntax making calling Dispose() easy. A typical usage looks like this:
using (var resource = new Resource()) {
resource.Use();
} // implicitly calls resource.Dispose()
This is roughly equivalent to
var resource = new Reource();
try {
resource.Use();
} finally {
resource.Dispose();
}
The differences are mostly too subtle to affect our discussion here.
Often, as with RAII, you want some resource to have the same lifetime as an instance of a class. In that case, you construct the IDisposable resource in the constructor for the new class, and you have that class also implement IDisposable and call Dispose() on the resource in its own Dispose() method. Actually, there are subtle, confusing and dangerous interactions between Dispose(), the actual destructor (well, finalizer), the garbage collector and OS resources, which make correctly implementing Dispose() rather tricky. Fortunately, for our purposes, all of these issues are too subtle to affect our discussion much.
I'm not comfortable really talking much about Java, as I'm much more familiar with C#, but recent versions of Java have an AutoCloseable interface and an extension to the "try" syntax which is similar to Disposable and "using".
C# has similar flexibility to C++ because Disposable is so similar to RAII. Here's a few examples of using resources and non-resources in C#:
public void ExampleUsingResource() {
using (var resource = new Resource() {
resource.Use();
}
}
public Resource ExampleUseOfEscapingResource() {
var resource = new Resource();
resource.Use();
return resource;
}
public void ExampleUseOfLeakingResource() {
var resource = new Resource();
resource.Use();
} // failed to release resource properly by calling Dispose()
public void ExampleUseOfNonResource() {
var nonResource = new NonResource();
nonResource.Use();
} // no need to release nonResource, since it does not implement IDisposable
So this is even trickier than in C++, since leaking a resource not only looks like a cross between "using" a resource and deliberately allowing a resource to escape the current scope; but it looks identical to using a non-resource. And here's where it gets really tricky: the difference between a non-resource and a resource is that a resource implements IDisposable. And how do you know that when you're coding? Well, if you're using Visual Studio with IntelliSense (or something roughly equivalent), usually you would try typing "resource.Disp" and seeing if the system wants to autocomplete it to Dispose. This almost works, except for three issues.
First, and pretty minor, is that it's possible to implement Dispose without implementing IDisposable, and then you can't use "using". But you find that out as soon as you try to use a "using" statement, so that's mostly ok.
Second, and pretty tricky, is that some classes explicitly implement IDisposable.Dispose, so that the Dispose method isn't available unless you cast to (IDisposable). That means it won't autocomplete so you can be fooled into not using "using".
Third, sometimes it is apparently safe not to call Dispose on certain IDisposable objects, such as Task. This turns out to be pretty convenient, since in many of the ways you are encouraged to use Tasks in recent C# versions, it's very hard to find the right way to Dispose them. But this means that sometimes the Leaking code doesn't really leak, which is pretty confusing.
Oh, and did I mention that sometimes Dispose throws?
What it adds up to is that the IDisposable pattern seems fine, but ends up being really painful to use safely. Fortunately, C# has pretty good lambda support, so you can easily implement and use RIFL.
public class RiflResource {
public static void WithResource(Action<RiflResource> useFunc) {
using (var raw = RawResource.Obtain()) { // often: new RawResource()
var rifl = new RiflResource(raw);
useFunc(rifl);
} // implicitly Dispose (Release)
}
public void Use() { raw_.Use(); }
private readonly RawResource raw_;
private RiflResource(RawResource raw) { raw_ = raw; }
}
And it's easy to use
public void ExampleRiflUsage() {
RiflResource.WithResource(resource => {
resource.Use();
});
}
In fact, of the languages I've used in examples, C# has the best syntax for anonymous functions, which makes this the cleanest usage. And I intentionally wrote my example so that it's obvious how to add multiple calls to Use; this simple case has an even briefer alternate syntax:
public void ExampleRiflUsage() {
RiflResource.WithResource(resource => resource.Use());
}
or even
public void ExampleRiflUsage() {
RiflResource.WithResource(Use);
}
but that only works if you want to call Use() alone once with no arguments.
I am really happy to wrap up my posts on RIFL. So far, I've only blogged on 2 topics, and each of those turned into multi-post discussions, where each post was too long. I hope to find a reasonable-length topic for the next post.
Subscribe to:
Posts (Atom)