Why not automatically infer constraints?

UPDATE: Whoops! I accidentally set a draft of this article to automatically publish on a day that I was away on vacation. The fact that it was (1) not purple and (2) introduced the topic and then stopped in mid-sentence were both clues that this was an unfinished edit. Sorry about that; I thought I had set it to *not* publish on Monday. I am not so good with these computer thingies apparently.

I spent the weekend not thinking about computers by lying beside a pool in Palm Springs, which I can definitively report is an awesome way to spend a rainy weekend in Seattle. I can also definitely report that Peter Frampton has lost almost all his hair and absolutely none of his talent; the man is amazing. If you like 1970's hard rock guitar solos, you've got just a couple more weeks to hear him perform all of Frampton Comes Alive.

Right, let's actually finish off that article then:


Suppose you have a generic base type with a constraint:

class Bravo<T> where T : IComparable<T> { ... }

If you make a generic derived class in the obvious way:

class Delta<U> : Bravo<U> { ... }

then the C# compiler gives you an error:

error CS0314: The type 'U' cannot be used as type parameter 'T' in the generic type or method 'Bravo<T>'. There is no boxing conversion or type parameter conversion from 'U' to 'System.IComparable<U>'.

Which seems reasonable; every construction of Bravo<T> is required to meet the constraints on T and we have no evidence whatsoever that the type supplied for U will meet those constraints.

But that's only one way of looking at the problem; another way of looking at it is that we do have evidence that the person who declared U expected that U meets the constraints of T, since they used it as T. Given that evidence one might reasonably then expect the compiler to simply silently place the same constraint upon U. U would then meet the constraint on T; any error then would not be on the declaration of Delta's base class, but rather, upon any code which constructs Delta<U> such that Bravo<T>'s constraints are violated.

I'm often asked why the compiler does not implement this feature or that feature, and of course the answer is always the same: because no one implemented it. Features start off as unimplemented and only become implemented when people spend effort implementing them: no effort, no feature. This is an unsatisfying answer of course, because usually the person asking the question has made the assumption that the feature is so obviously good that we need to have had a reason to not implement it. I assure you that no, we actually don't need a reason to not implement any feature no matter how obviously good. But that said, it might be interesting to consider what sort of pros and cons we'd consider if asked to implement the "silently put inferred constraints on class type parameters" feature.

As C# has evolved, clearly more and more features involve the compiler silently making inferences on your behalf: method group conversions, method type inference, implicitly typed locals, implicitly typed arrays, implicitly typed lambdas, and so on, all involve a great deal of inference work by the compiler. You would think we could do the same thing for generic type constraints. However, the problem is not as easy as it looks. The easy case mentioned above is, well, easy. Things quickly get complicated:

class Echo<W, X> where W : IComparable<W> where X : IComparable<X> { }
class Foxtrot<Y> : Echo<Y, Y> { }

The supposition here is now that Y must be implicitly constrained to be both IComparable<Y> and... IComparable<Y>.  OK, that seems reasonable. But what if we then change that to:

class Echo<W, X> where W : Foo where X : Bar { }
class Foxtrot<Y> : Echo<Y, Y> { }

Suppose Foo and Bar are class types with no relationship between them. Now there is no possible type argument for Y that meets the inferred constraint. Is the compiler now required to tell you that fact? How smart does it have to be about that?

Let's make this a bit more complicated. I'll just make something up off the top of my head:

class Golf<T> where T : Hotel<T> {}
class Hotel<U> : Golf<Indigo<U>> where U : IJuliet<Hotel<U>> {}
class Indigo<V> : Hotel<Golf<V>> where V : IKilo<V> {}
interface IJuliet<W> {}
interface IKilo<X>

What are the constraints that we have to infer? Well for T to be a Hotel<T>, T has got to be an IJuliet<Hotel<T>>, so we should add that constraint. But in order to be an IJuliet<Hotel<T>>, T has got to meet the constraints on Hotel<T>... oh, wait, we already added that constraint. Looks like our constraint adder is going to have to be resistant to cycles. But wait, do we have *all* the constraints on Hotel<T>? So far we have only evaluated the *stated* constraints. U in Hotel<U> not only has to be an IJuliet<Hotel<U>>, it also has to be a legal Indigo<U>, which means it needs to be an IKilo<U>, which means we should add a constraint to T that T be IKilo<T>.

And so on. I'm not going to go through a full analysis of this thing. The point is, the analysis is complicated, the analysis may go into infinite loops very easily, and it is not at all clear when you know that you've worked out the full set of type constraints when types refer to each other in arbitrary ways. (And we haven't even considered what happens if the interfaces are covariant or contravariant!) I don't like adding inference algorithms to the compiler that require cycle detection and do potentially unbounded amounts of work. The possibility of getting the algorithm wrong is very real. Even if we get the algorithm right, the odds that the implementation will be buggy is very high. If we add this feature then the compiler needs to be able to get the right answer all the time, and to give a sensible error message that helps the user if there is no right answer. Both problems seem very difficult.

Moreover, why are we stopping with base types? It seems like if we're going to do the feature of inferring constraints, then we ought to consume information about the entire class, not just the base types:

class Mike<T> where T : November {}
class Oscar<V> { Mike<V> bv; }

Should we deduce that V must be November because Oscar<V> has a field of type Mike<V>? I don't see why the base types are any more special than the field types.

What if Oscar<V> had used Mike<V> as a return type of a method? Or a parameter type? Or as the type of a local variable? Where does it stop?

Basically, the feature is too much pain for too little gain. When you construct a generic, you are the one required to supply a proof to the compiler that you have satisfied the constraints. C# is not a "figure out what the developer meant to say and say it for them" language; it's a "tell the developer when they have supplied too little information" language.

Comments (27)
  1. Paul Irwin says:

    Isn't it better to be explicit sometimes? Say you had the following:

    interface ITango<T> where T : IDisposable { … }

    class Bravo<T> where T : IComparable<T> { … }

    class Delta<U> : Bravo<U>, ITango<U> { … }

    That would mean that U now has to be both IDisposable and IComparable<T>. In the multiple-inheritance case, I think the current implementation makes more sense to not infer the constraint and be explicit, so that you have to declare it as:

    class Delta<U> : Bravo<U>, ITango<U> where U : IDisposable, IComparable<U> { … }

    If it inferred it for you, and the ITango interface added IEnumerable to its constraints, it would break the downstream caller rather than Delta, which would be just as messy as dynamic code can be.

  2. pete.d says:

    I'm not against inferences generally. However, in this case I think that the non-feature is actually better, because it places a potential error closer to the root cause.

    If you infer the constraint, then the user can get into trouble if they fail to provide a satisfactory type when using Delta<U>. The error will be correct, but if the user goes and looks at the declaration of Delta<U>, there won't be anything there to tell them why the constraint exists. They have to go look at Bravo<T>.

    In this small example, that's not hard to do. But what about a more complicated scenario in which there are several inherited/implemented types? Then the user has to go look at each one to see if any have a constraint from which the Delta<U> constraint is being inferred.

    The way it is now, if you fail to declare the constraint, you get a specific, (somewhat) clear message explaining that the constraint hasn't been met, and which constraint for that matter.

  3. Allan says:

    I feel like the second of the last paragraph could be the inscription on a very interesting book…

  4. Jon Skeet says:

    "As C# has evolved, clearly more and more features involve the compiler silently making inferences"

    Presumably this means that in C# 10, if the compiler infers that you're a truly terrible coder, it will add code to make sure it can never do any harm in a production environment? Or perhaps Visual Studio should have a Clippy-like helper: "It looks like you have no clue what you're doing – would you like to ask on Stack Overflow?"

    On a more serious note… has your computer run out of purple pixels? I know you've been using a lot of them, but even so…

  5. gsvick@gmail.com says:

    @Allan, I think there is something much more important missing in the article: purple!

  6. gsvick@gmail.com says:

    Ignore my previous comment.

  7. michael@stum.de says:

    To be fair, I'd much rather see constructor inheritance than automatic constraint inferring 🙂

  8. carter.jk@gmail.com says:

    Ditto @Michael Stum!  Constructor inheritance +1 vote.

  9. Bolt says:

    "The answer to 'Why doesn’t this feature exist?' is usually 'By default features don’t exist. Somebody has to implement them.'

    It’s not like every feature you can think of comes out of your brain fully tested and implemented, and then some PM somewhere files a bug to have your feature removed. Features start out nonexistent and somebody has to make them happen."

    Raymond Chen


  10. admeralthrawn@hotmail.com says:

    One additional reason to keep the constraint explicit is to mitigate some issues with the brittle base class problem.  If generic type constraints are inherited implicitly, then removing a constraint becomes a potentially breaking change for all inheritors of your class, which to me feels wrong.

  11. jweber says:

    On the con side, I agree with the consensus that explicit contraints are better in complex cases.

    However, on the pro side, I'm sure that code that has explicit constraints (granted those are compatible with the constraints inferred) will remain valid. Therefore a developer can still be explicit if he/she wants to.

  12. Rafael says:

    Hi Erick, in a previous post "blogs.msdn.com/…/the-future-of-c-part-five.aspx" you commented "calling base methods from anonymous methods: Yes, we fixed that." I think you fixed it for C#4… what happened for C#3.5??? did you do a patch???

  13. Rafael says:

    Sorry for asking about that feature in a post for another feature… I just thought you wouldn't read 4-years-ago posts anymore

  14. Wouter de Kort says:

    When I read this blog post, I remembered having this discussion on Stackoverflow(stackoverflow.com/…/997973) What happened to the arguments you mentioned?

    Personally, I think automatically inferring constraints, would make the code harder to understand. But the IDE could offer a smart-tag to insert the constraints automatically instead of having to copy them manually from a base class. Maybe even support it as a refactor option (Add constraint to derived classes). The same could be done for constructor inheritance!

  15. Andrew Bingham says:

    I take the opposite view – I don't want ANY defaults for ANY programming element. Ever.

    Main reason: If you did not intend the Default – and the compiler "silently implements it" then somewhere down stream a defect will occur. Good example – default modifiers in C#

    Second reason: Too many languages, too many defaults to remember

    Third reason: When learning a language "silent compiler actions" are just plain confusing

  16. alitamur@yahoo.com says:

    I couldn't find time to read stackoverflow.com/…/8606679 very carefully, and I don't claim that I understand all the implications of the proposal but since you asked, I'm all for it.

    Anything that makes the compiler smarter on type inference is welcome. The introduction of "var" has made coding easier and also made the code more readable. And anything that makes the type system more powerful (as in covariant&contravariant contraints in C# 4.0) is welcome.

    I'd like the type system be more powerful. I wish we had multiple dispatch. I wish we had more powerful constraints. I wish we had overloading based on return types of methods. Hell, I wish we had something like Duck Typing, i.e. if I call x.Foo(), then infer that x is implementing IFoo(), and every class with a Foo() method automatically implements IFoo(). I'd like C# code to be as flexible as a dynamic typed language like Smalltalk, the compiler to be more clever on type inference than ML compilers, and still be safe, and perform as few run-time type-checks as logically possible.

    "To let the compiler infer too much is dangerous" many people say. I disagree. Let the compiler be smart but also very transparent. No implicit type casts, or at least make it (optionally) visible on the screen. No implicit boxing, or at least make it (optionally) visible on the screen. If it produces C# code as an intermediate step when it transforms LINQ expressions, let me be able to see that intermediate step on the screen. If it makes an inference about a generic type T, let me be able to see that inference. Then we get the best of all worlds. A clean, short, readable code, a powerful type system, a smart compiler, and ability to see what is wrong when something is wrong.

    By the way, Eric, I enjoy and learn from your blog and answers at stackoverflow very much, I hope I don't sound like whining or bickering; I'm not an expert and I may not be correct in terminology or sensible in my wishes but anyway this is what they are.

    Your proposal to make inferences on type constraints is not as large as my overgrown and unrealistic wishes but it is a step in the right direction.

  17. David says:

    You are being WAY to literal. I used to be just like you. It took me years to get my head out of logic text book and do the obvious translations in my head, "Why doesn't the compiler implement X". Come on! You're clearly smart enough to realize that they did not literally mean the question they asked. Why not answer the question they really wanted an answer to instead? Everyone, including you, would be much happier. Just say, "We considered the pros and cons of X and it did not meet the bar to be implemented." Or "That's a great idea. We'll have to consider all the pros and cons…." Then you can have a nice and polite conversation about those pros and cons….instead of what you appear to do now, which is be an ___ ____.

  18. Mark Lysaght says:

    @David. Be nice.

    As far as I'm concerned Eric can be as literal as he damn well pleases, I am simply grateful that he shares his experiences with us, if he wants he can type each post shifted one key to the right ;olr yjos, and I'd still read them.

    Now stop being an sdd jp;r, shut up, and leave the nice man alone.

  19. Juozas says:

    Am I the only one thinking that this post is actually unfinished draft that was accidentally submitted? And judging by Eric's absense in the comments he might actually be not aware of this.

  20. Samuel Warren says:

    Eric, the assumption that your user's tend to make (that you should have had a good reason NOT to implement feature "x") is very common in all development. I find it amusing that software developers make this same assumption, yet when we're writing software we tend to get angry when OUR users make that assumption. It's rather ironic if you think about it.

  21. Aaron says:

    @Juozas No, you're not the only one. It does seem like the post is cut off at the end.

  22. ferd@incore.nl says:

    Base<T> where T: class // in someone else's dll

    D<T> : Base<T> { // infers constrains

          M(T t) {

                if (t==null) {.   //.  Whoops!!  If base changes it's constraints, this might not work

  23. Konstantin says:

    @Jon Skeet : "It looks like you have no clue what you're doing – would you like to ask on Stack Overflow?"

    Eric, this is the best feature request I have seen in the blog comments so far! Please implement in the next version of C# compiler!

  24. Kyle says:

    @David: "I'll look into that feature" is not an answer to "Why isn't this feature implemented?"

    @Konstantin: That could probably be a plug-in… get coding 🙂

  25. Olivier says:

    The examples you gave definitely helped me understand why too much inference of type constraint is a bad idea. I think your last paragraph could be more detailed about it.

    This problem is, the inference feature would conflict with other inferences features, and then error reporting would become bad, because when writing error messages, the compiler wouldn't know where the code is supposed to be wrong. In C# with VS, the quality of the error messages are an important and polished part of the programming experience, so the best tradeoff is to not infer type constraints.

    Everything cannot be infered anyway, sometimes you have to have the programmer state some things explicitly. And for type constraints, the class declaration is a very good place to do so.

  26. Ralph Becket says:

    Hmm, two things come to mind.

    First, these type constraints are veeery similar to what you find in languages with type classes, such as Haskell and Mercury, and they have solved the inference problem.  I don't think it's as big a can'o'worms as you imagine (of course, I may have missed something: OO type systems tend to be full of strange corners).

    Second, since you have to *check* these constraints in the compiler, haven't you already done much of the inference work here?

  27. qwertman@hotmail.com says:

    There is a middle ground between having the compiler infer something and requiring you to state your requirements explicitly, and that middle ground is named clippy 😉

    Actually what I mean is, if a compiler or "lint" tool can find a change that will fix a problem — whether it be adding a type constraint, adding a cast, adding a semicolon, or changing "var Z = new…" to "Dictionary<string, object> Z = new…" because Z is a field, then the error message in the IDE should have a little "autocorrect" button beside it.

    The advantages of this approach compared to "inferring what you meant" is (A) less work for Eric Lippert — the compiler doesn't have to handle all cases, only the most common ones, (B) the inferred information becomes part of the source code so the developer can see it, (C) any inference bugs are never serious–the developer can correct the compiler's guess if it guesses wrong.

Comments are closed.

Skip to main content