33

I have these two little programs:

C

#include <stdio.h>
int main()
{
    if (5) {
        printf("true\n");
    }
    else {
        printf("false\n");
    }

    return 0;
}

Java

class type_system {
   public static void main(String args[]) {
       if (5) {
           System.out.println("true");
       }
       else {
           System.out.println("false");
       }
   }
}

which reports the error message:

type_system.java:4: error: incompatible types: int cannot be converted to boolean
       if (5) {
           ^
1 error

My understanding

So far, I understood this example as a demonstration of the different type systems. C is more weakly typed and allows conversion from int to boolean without errors. Java is more strongly typed and fails, because no implicit conversation is allowed.

Therefore, my question: Where did I misunderstand things?

What I'm not looking for

My question is not related to bad coding style. I know it's bad, but I'm interested in why C does allow it and Java not. Therefore, I'm interested in the language's type system, specifically its strongness.

  • 22
    @toogley: What is it about the type system in Java that you specifically want to know? The type system doesn't allow this because the language specification forbids it. The reasons why C allows it and Java does not have everything to do with what's considered acceptable in both languages. The resulting behavior of the type systems is the effect of that, not the cause. – Robert Harvey May 11 '17 at 20:35
  • 8
    @toogley: why do you think you misunderstood something? Reading the text again, I don't understand what your question is. – Doc Brown May 11 '17 at 20:37
  • 26
    Actually, this is not an example of weak typing in C. In the past (C89), C did not even have a boolean type, so all "boolean" operations actually operated on int values. A more proper example would be if (pointer). – Rufflewind May 11 '17 at 20:48
  • 5
    I downvoted because it doesn't seem like much research was done trying to understand this. – jpmc26 May 11 '17 at 22:57
  • 11
    It seems that the original question asked is a bit different than the edited version (which is why some of the comments and answers seem to be answering a different question). In its current form, there does not seem to be a question here. What misunderstanding? Java is behaving exactly as you think it should be. – jamesdlin May 12 '17 at 3:10
135

1. C and Java are different languages

The fact that they behave differently should not be terribly surprising.

2. C is not doing any conversion from int to bool

How could it? C didn't even have a true bool type to convert to until 1999. C was created in the early 1970s, and if was part of it before it was even C, back when it was just a series of modifications to B1.

if wasn't simply a NOP in C for nearly 30 years. It directly acted on numeric values. The verbiage in the C standard (PDF link), even over a decade after the introduction of bools to C, still specifies the behavior of if (p 148) and ?: (p 100) using the terms "unequal to 0" and "equal to 0" rather than the Boolean terms "true" or "false" or something similar.

Conveniently, ...

3. ...numbers just happen to be what the processor's instructions operate on.

JZ and JNZ are your basic x86 assembly instructions for conditional branching. The abbreviations are "Jump if Zero" and "Jump if Not Zero". The equivalents for the PDP-11, where C originated, are BEQ ("Branch if EQual") and BNE ("Branch if Not Equal").

These instructions check if the previous operation resulted in a zero or not and jump (or not) accordingly.

4. Java has a much higher emphasis on safety than C ever did2

And, with safety in mind, they decided that restricting if to booleans was worth the cost (both of implementing such a restriction and the resulting opportunity costs).


1. B doesn't even have types at all. Assembly languages generally don't, either. Yet B and assembly languages manage to handle branching just fine.

2. In the words of Dennis Ritchie when describing the planned modifications to B that became C (emphasis mine):

...it seemed that a typing scheme was necessary to cope with characters and byte addressing, and to prepare for the coming floating-point hardware. Other issues, particularly type safety and interface checking, did not seem as important then as they became later.

  • 13
    Nice point about boolean expressions in C mapping directly to processor instructions. I hadn't thought about that before. – Robert Harvey May 11 '17 at 21:39
  • 17
    I think point 4 is misleading as it seems to imply C has some smidgen of safety focus... C will let you do basically anything you want: C assumes you know what you're doing, often to spectacularly disastrous results. – TemporalWolf May 12 '17 at 0:38
  • 13
    @TemporalWolf You state that as if C letting you do what you want was bad. The difference in my opinion is that C was written for programmers and a basic competence is expected. Java is written for code monkeys and if you can type you can program in it. Often spectacularly badly but who cares right? I will never forget when a teacher in the introductory java class i was forced to take as a part of my CS minor, pointed out that using recursion to compute the Fibonnaci numbers might be useful because it was "easy to write". That's why we have the software we have. – DRF May 12 '17 at 5:03
  • 25
    @DRF One of my professors for a C networking class said "C is like a box of hand grenades with all the pins pulled out." You've got a lot of power, but it's essentially guaranteed to blow up in your face. In the gross majority of cases, it's not worth the hassle, and you'll be far more productive with a higher-level language. Have I transcoded Python into hacky C bit-twiddling to get a 6 order of magnitude increase in speed? Yes, yes I have. But that is the exception, not the rule. I can accomplish in a day in Python what would take me two weeks in C... and I'm a decent C programmer. – TemporalWolf May 12 '17 at 7:36
  • 11
    @DRF Given the prevalence of buffer overflow exploits in mainstream software developed by top name companies, apparently even programmers with basic competence can't be trusted not to shoot off their feet. – Solomonoff's Secret May 12 '17 at 15:46
14

C 2011 Online Draft

6.8.4.1 The if statement

Constraints

1    The controlling expression of an if statement shall have scalar type.

Semantics

2    In both forms, the first substatement is executed if the expression compares unequal to 0. In the else form, the second substatement is executed if the expression compares equal to 0. If the first substatement is reached via a label, the second substatement is not executed.

3    An else is associated with the lexically nearest preceding if that is allowed by the syntax.

Note that this clause specifies only that the controlling expression shall have a scalar type (char/short/int/long/etc.), not specifically a Boolean type. A branch is executed if the controlling expression has a non-zero value.

Compare that with

Java SE 8 Language Specification

14.9 The if Statement

The if statement allows conditional execution of a statement or a conditional choice of two statements, executing one or the other but not both.
    IfThenStatement:
        if ( Expression ) Statement

    IfThenElseStatement:
        if ( Expression ) StatementNoShortIf else Statement

    IfThenElseStatementNoShortIf:
        if ( Expression ) StatementNoShortIf else StatementNoShortIf
The Expression must have type boolean or Boolean, or a compile-time error occurs.

Java, OTOH, specifically requires that the controlling expression in an if statement have a Boolean type.

So, it's not about weak vs. strong typing, it's about what each respective language definition specifies as a valid control expression.

Edit

As for why the languages are different in this particular respect, several points:

  1. C was derived from B, which was a "typeless" language - basically, everything was a 32- to 36-bit word (depending on the hardware), and all arithmetic operations were integer operations. C's type system was bolted on a bit at a time, such that...

  2. C didn't have a distinct Boolean type until the 1999 version of the language. C simply followed the B convention of using zero to represent false and non-zero to represent true.

  3. Java post-dates C by a good couple of decades, and was designed specifically to address some of C's and C++'s shortcomings. No doubt tightening the restriction on what may be a control expression in an if statement was part of that.

  4. There's no reason to expect any two programming languages to do things the same way. Even languages as closely related as C and C++ diverge in some interesting ways, such that you can have legal C programs that are not legal C++ programs, or are legal C++ programs but with different semantics, etc.

  • Too sad, i can't mark two answers as "accepted".. – toogley May 12 '17 at 16:52
  • 1
    Yes, this is a good restatement of the question. But the question asked why there's this difference between the two languages. You haven't addressed this at all. – Dawood ibn Kareem May 13 '17 at 2:07
  • @DawoodibnKareem: The question was why C allowed conversion from int to boolean while Java didn't. The answer is that there's no such conversion in C. The languages are different because they are different languages. – John Bode May 13 '17 at 3:17
  • Yes, "it's not about weak vs. strong typing". Just look at auto-boxing and auto-unboxing. If C had those, it couldn't allow any scalar type either. – Deduplicator May 13 '17 at 17:39
5

Many of the answers seem to be targeting the embedded assignment expression that is within the conditional expression. (While that is a known kind of potential pitfall, it is not the source of the Java error message in this case.)

Possibly this is because the OP did not publish the actual error message, and, the ^ caret points directly to the = of the assignment operator.

However the compiler is pointing to the = because it is the operator that produces the final value (and hence the final type) of the expression that the conditional sees.

It is complaining about testing a non-boolean value, with the following kind of error:

error: incompatible types: int cannot be converted to boolean

Testing integers, while sometimes convenient, is considered a potential pitfall that the Java designers choose to avoid. After all, Java has a true boolean data type, which C does not (it has no boolean type).

This also applies to C's testing pointers for null/non-null via if (p) ... and if (!p) ..., which Java similarly does not allow instead requiring an explicit comparison operator to obtain the required boolean.

  • 1
    C does have a boolean type. But that's newer than the if statement. – MSalters May 12 '17 at 7:54
  • 3
    @MSalters C's bool is still an integer under covers, hence you can do bool b = ...; int v = 5 + b;. This is different to languages with a full boolean type which cannot be used in arithmetic. – Jules May 12 '17 at 9:55
  • C's boolean is just a really small integer. "true" and "false" could easily be defined with macros or whatever. – Oskar Skog May 12 '17 at 12:49
  • 4
    @Jules: You're just pointing out that C has weak type-safety. Just because a boolean type converts to an integer value doesn't mean it is an integer type. By your logic, integer types would be floating-point types since int v = 5; float f = 2.0 + v; is legal in C. – MSalters May 12 '17 at 13:40
  • 1
    @MSalters actually _Bool is one of the standard unsigned integer types as defined by the Standard (see 6.2.5/6). – Ruslan May 12 '17 at 15:18
2

incompatible types: int cannot be converted to boolean

I'm interested in why C does allow it and java not. Therefore, I'm interested in the language's type system, specifically its strongness.

There are two parts to your question:

Why does Java not convert int to boolean?

This boils down to Java being intended to be as explicit as possible. It is very static, very "in your face" with its type system. Things that are automatically type-cast in other languages are not so, in Java. You have to write int a=(int)0.5 as well. Converting float to int would lose information; same as converting int to boolean and would thus be error-prone. Also, they would have had to specify a lot of combinations. Sure, these things seem to be obvious, but they intended to err on the side of caution.

Oh, and compared to other languages, Java was hugely exact in its specification, as the bytecode was not just an internal implementation detail. They would have to specified any and all interactions, precisely. Huge undertaking.

Why does if not accept other types than boolean?

if could perfectly well be defined as to allow other types than boolean. It could have a definition that says the following are equivalent:

  • true
  • int != 0
  • String with .length>0
  • Any other object reference that is non-null (and not a Boolean with value false).
  • Or even: any other object reference that is non-null and whose method Object.check_if (invented by me just for this occasion) returns true.

They didn't; there was no real need to, and they wanted to have it as robust, static, transparent, easy to read etc. as possible. No implicit features. Also, the implementation would be pretty complex, I'm sure, having to test each value for all possible cases, so performance just may have played a small factor as well (Java used to be sloooow on the computers of that day; remember there was no JIT compilers with the first releases, at least not on the computers I used then).

Deeper reason

A deeper reason could well be the fact that Java has its primitive types, hence its type system is torn between objects and primitives. Maybe, if they had avoided those, things would have turned out another way. With the rules given in the previous section, they would have to define the truthiness of every single primitive explicitely (since the primitives don't share a super class, and there is no well defined null for primitives). This would turn into a nightmare, quickly.

Outlook

Well, and in the end, maybe it's just a preference of the language designers. Each language seems to spin their own way there...

For example, Ruby has no primitive types. Everything, literally everything, is an object. They have a very easy time making sure that every object has a certain method.

Ruby does look for truthiness on all types of objects you can throw at it. Interestingly enough, it still has no boolean type (because it has no primitives), and it has no Boolean class either. If you ask what class the value true has (handily available with true.class), you get TrueClass. That class actually does have methods, namely the 4 operators for booleans (| & ^ ==). Here, if considers its value falsey if and only if it is either false or nil (the null of Ruby). Everything else is true. So, 0 or "" are both true.

It would have been trivial for them to create a method Object#truthy? which could be implemented for any class and return an individual truthiness. For example, String#truthy? could have been implemented to be true for non-empty strings, or whatnot. They didn't, even though Ruby is the antithesis of Java in most departments (dynamic duck-typing with mixin, re-opening classes and all that).

Which might be surprising to a Perl programmer who is used to $value <> 0 || length($value)>0 || defined($value) being the truthiness. And so on.

Enter SQL with its convention that null inside any expression automatically makes it false, no matter what. So (null==null) = false. In Ruby, (nil==nil) = true. Happy times.

  • Actually, ((int)3) * ((float)2.5) is quite well-defined in Java (it is 7.5f). – Paŭlo Ebermann May 13 '17 at 8:57
  • You're right, @PaŭloEbermann, I have deleted that example. – AnoE May 13 '17 at 10:44
  • Woah there... would appreciate comments from the downvoters and the one who voted to actually delete the answer. – AnoE May 13 '17 at 10:44
  • Actually conversion from int to float also loses information in general. Does Java also forbid such implicit cast? – Ruslan May 14 '17 at 5:27
  • @Ruslan no (same for long → double) – I guess the idea is that the potential information loss there is only in the least significant places, and only happens in cases which are deemed not so important (quite large integer values). – Paŭlo Ebermann May 14 '17 at 10:44
1

In addition to the other fine answers, I'd like to talk about the consistency between languages.

When we think of a mathematically pure if-statement, we understand that the condition can be either true or false, no other value. Every major programming language respects this mathematical ideal; if you give a Boolean true/false value to an if-statement, then you can expect to see consistent, intuitive behavior all the time.

So far so good. This is what Java implements, and only what Java implements.

Other languages try to bring conveniences for non-Boolean values. For example:

  • Suppose n is an integer. Now define if (n) to be shorthand for if (n != 0).
  • Suppose x is a floating-point number. Now define if (x) to be shorthand for if (x != 0 && !isNaN(x)).
  • Suppose p is a pointer type. Now define if (p) to be shorthand for if (p != null).
  • Suppose s is a string type. Now define if (s) to be if (s != null && s != "").
  • Suppose a is an array type. Now define if (a) to be if (a != null && a.length > 0).

This idea of providing shorthand if-tests seems good on the surface... until you run into differences in designs and opinions:

  • if (0) is treated as false in C, Python, JavaScript; but treated as true in Ruby.
  • if ([]) is treated as false in Python but true in JavaScript.

Each language has its own good reasons for treating expressions one way or another. (For example, the only falsy values in Ruby are false and nil, hence 0 is truthy.)

Java adopted an explicit design to force you to supply a Boolean value to an if-statement. If you hastily translated code from C/Ruby/Python to Java, you cannot leave any lax if-tests unchanged; you need to write out the condition explicitly in Java. Taking a moment to pause and think can save you from sloppy mistakes.

  • 1
    Do you know that x != 0 is the same as x != 0 && !isNaN(x)? Also, it's normally s != null for pointers, but something else for non-pointers. – Deduplicator May 13 '17 at 18:09
  • @Deduplicator in which language is that the same? – Paŭlo Ebermann May 14 '17 at 10:55
  • 1
    Using IEEE754, NaN ≠ 0. NaN ≠ anything at all. So if (x) would execute, and if (! x) would execute as well... – gnasher729 May 14 '17 at 11:32
0

Well, every scalar type in C, pointer, boolean (since C99), number (floating-point or not) and enumeration (due to direct mapping to numbers) has a "natural" falsey value, so that's good enough for any conditional expression.

Java has those too (even if Java pointers are called references and are heavily restricted), but it introduced auto-boxing in Java 5.0 which muddles the waters unacceptably. Also, Java-programmers exhort the intrinsic value of typing more.

The one error which spawned the debate whether restricting conditional expressions to boolean type is the typo of writing an assignment where a comparison was intended, which is not addressed by restricting the conditional expressions type at all, but by disallowing use of a naked assignment-expression for its value.

Any modern C or C++ compiler handles that easily, giving a warning or error for such questionable constructs if asked.
For those cases where it is exactly what was intended, adding parentheses helps.

To summarize, restricting to boolean (and the boxed equivalent in Java) looks like a failed attempt to make a class of typos caused by choosing = for assignment compile-errors.

  • Using == with boolean operands, of which the first one is a variable (which then could by typo'ed to =) inside an if happens much less often than with other types, I would say, so it is only a semi-failed attempt. – Paŭlo Ebermann May 14 '17 at 10:51
  • Having 0.0 -> false and any value other than 0.0 -> true doesn't look "natural" to me at all. – gnasher729 May 14 '17 at 11:30
  • @PaŭloEbermann: Partial result with unfortunate side-effets, while there is an easy way to achieve the aim without side-effects, is a clear failure in my book. – Deduplicator May 14 '17 at 11:42
  • You're missing the other reasons. What about the truthy value of "", (Object) "", 0.0, -0.0, NaN, empty arrays, and Boolean.FALSE? Especially the last one is funny, as it's non-null pointer (true) which unboxes to false. +++ I also hate writing if (o != null), but saving me a few chars every day and letting me to spend half a day debugging my "clever" expression is no good deal. That said, I'd love to see some middle way for Java: More generous rules but leaving no ambiguity at all. – maaartinus May 14 '17 at 18:06
  • @maaartinus: As I said, the introduction of auto-unboxing in Java 5.0 meant if they assigned a truth-value corresponging to null-ness to pointers, they would have quite a problem. But that's a problem with auto-(un-)boxing, not anything else. A language without auto-(un-)boxing like C is happily free of that. – Deduplicator May 14 '17 at 18:15
-1

My question is not related to bad coding style. I know its bad, but i'm intersted in why C does allow it and java not.

Two of the design goals of Java were:

  1. Allow the developer to focus on the business problem. Make things simpler and less error-prone, e.g. garbage collection so developers don't need to focus on memory leaks.

  2. Portable between platforms, e.g. run on any computer with any CPU.

Use of assignment as an expression was known to cause a lot of bugs due to typos, so under goal #1 above, it is not allowed the way you are trying to use it.

Also, inferring that any nonzero value = true and any zero value = false is not necessarily portable (yes, believe it or not, some systems treat 0 as true and 1 as false), so under goal #2 above, is not allowed implicitly. You can still cast explicitly of course.

  • 4
    Unless it's been added in Java 8, there is no explicit cast between numeric types and Booleans. To convert a numeric value to a Boolean you have to use a comparison operator; to convert a Boolean value to a numeric one you typically use the ternary operator. – Peter Taylor May 12 '17 at 6:09
  • You could make a case for disallowing use of an assignment for its value due to those typos. But considering how often you see == true and == false in Java, obviously limiting the controlling expression to (optionally boxed) boolean type doesn't help that much. – Deduplicator May 13 '17 at 17:44
  • Java would trivially ensure portability w.r.t. to "some systems treat 0 as true and 1 as false" as it's Java's zero and Java's false. It really doesn't matter what the OS thinks about it. – maaartinus May 14 '17 at 18:10
-1

As an example what other languages do: Swift requires an expression of a type that supports the "BooleanType" protocol, which means it must have a "boolValue" method. The "bool" type obviously supports this protocol and you can create your own types supporting it. Integer types don't support this protocol.

In older versions of the language optional types supported "BooleanType" so you could write "if x" instead of "if x != nil". Which made using an "Optional bool" very confusing. An optional bool has values nil, false or true and "if b" would be not executed if b was nil and executed if b was true or false. This is not allowed anymore.

And there seems to be one guy who real dislikes opening your horizons...

protected by gnat May 12 '17 at 20:07

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.