 There's a new programming language on the horizon and it's called practical We've got Shahar Shimesh who will Introduce this language to us and explain the drivers behind its development So the title of his talk is the practical programming language or why C++ integers are fundamentally broken So let's give him an ear Thank you So yeah, the title of the of the talk is designed to Lure you in into learning new stuff not only about the new language But also about why I wrote it, but I'm actually going to try and live up to this promise so Essentially, I'm starting by talking about two languages that I'm hoping you've heard of that are called the C programming language and C++ and I'll actually start by trying to ask you a question, which is doesn't work as well over this Over this medium, but we'll do what we can and the question is this If we look at this program, which I'm really really quickly typing out Which is we accept to bite arguments and We'll call them a and b and We return the following very very complicated calculation on them Which is essentially the average of the two and the question is if I do Average of Two and four What will the program do what will it print and I think most of you will be able to figure out that it will print the average which which is Three, so let's just make sure that I haven't screwed it up somewhere And Hey So far I'm doing great. Now. Here's the interesting question Instead of two and four. What if I ask for the average in 254 and 252 and I'll give you a few seconds to answer that on IRC People who are complaining that you can't see it I'll try to increase the font size and better Okay So if I ask People are still complaining that they can't see does anyone get to the video Without video that won't work Okay, I'm gonna paste the program to the IRC channel. I Can do that and Do you have video now? Okay, can you hear now? Okay, so this is the program and the question is What does it do and people are already pointing out that it overflows and yes It overflows of over an unsigned integer and therefore it is not undefined behavior so the obvious answer would be 253 but One still no audio Okay, I'm not my own not on my end this time So the obvious answer should be 253 because that's the average of 252 and 254 but due to unsigned we expect to get 250 divided by 2 so 175 and Let's see what happens when we actually run the program. So for some reason We're actually getting 253 and That some reason is actually the topic of my my talk today. So You all figured out that a single byte overflows if I add 254 to her 252 and We would expect it to return 125 But we don't we get 253 and the reason for that Has to do with the C's origin now if you'll recall or if you knew a When C originally came out the syntax looked something like this So and No, it doesn't depend on the compiler. That's actually the way C is defined them There is no implementation defined behavior in the program. I wrote It is fully defined by the spec When C originally came out The syntax looked like this So what we see here is a function definition The arguments have no type the type is defined later So the argument the arguments type isn't part of the functions declaration and The function has no return value And if I try to compile this It compiles fine, but it does give a warning which is very revealing. It says return type defaults to int and The reason I point this out is because Not only return types KNRC the original C whenever it had any chance to Figure out something or to guess something the default go-to type for it was int and and that behavior even though KNRC is now K longer Staying on our history that behavior actually carries out not only to more than C But also to more than C plus plus so what happened with our previous program was that A is a unit 8 and B is a unit n But the moment I did any operation on them the compiler promotes them to integer Promotes them to integer and then After it divides by two it gets an integer which means That what we need to do next what the compiler needs to do next is to cast Implicitly from an integer 32 bytes signed integer to an 8-byte To 32 bit to 8 bit unsigned and it does so silently and The silently is the part. I'm actually want to talk about so We are actually can can write a program that shows that in a very Intense way because if we do STD a program that is very in your face We get a 16-bit integer and we just return it as an 8-bit integer And now if we try to compile this it compiles without a warning Even if we ask for extra warnings it compiles without a warning even if we say fine C is a bit of a See is a bit lenient in that front. Let's compile it as a C++ program It compiles without warning even and it's not that's not a GCC thing Yeah 19 isn't Isn't out yet so this program not only is it Perfectly legal C and C++ program it actually is warning free despite the fact that it does something that We really would like to get a warning about and the reason that's the case is because Due to the fact that the C semantics promotes everything to int This means that The compiler has no choice but to truncate integers to smaller sizes without a warning and That last part isn't 100% Accurate first of all some compilers in some circumstances do issue warning but I think the more interesting part is that other languages tried using a different model of operation so they try to To see whether there's a way to solve this this Plug this whole so that We don't have implicit we don't have implicit truncation and The reason this is important is because of the definition of the integer types in C C defines Basically, it says this it says as Char is 8 bit short is Greater or the same as char int is the greater of the same as short and long is greater than the same as int and with the original days of of 16 bit That that meant just that but today with 64 bit we pretty much say that short is 16 and int is 32 and long is 64 and that last part is very important It means that the type we see as the default integer type the type we promote to Isn't the small the largest type of integer we normally work with it isn't even the native type that the machine has and Therefore the truncation becomes a bit of a problem and Language that tried to handle this Was okay. Sorry. Here we go The d-language tried to handle this Trying to get rid of by But the d-language has among other thing to Two rules that it lives by The first is no narrowing conversions and the definition of a narrowing conversion in D is a conversion or implicit narrowing conversions is a conversion that converts from type with a certain amount of bits to a type with less with smaller amount of bits and what that means is that For example a conversion between a 16-bit unsigned integer and a 16-bit signed integer is not considered narrowing conversion and can be done implicitly but more importantly It has another law which says an expression that is both legal in C and Compiles in D should have the same meaning as in C Now on its own this sounds like a very good law because I mean It means that you can just copy paste code from C and if it compiles it's supposed to work But what this means is that D inherited the implicit promotion to int rule We just saw and we just saw that people are not even aware it exists But in conjunction with the first rule What this means is that as you type your code if you do any sort of integer manipulation You keep getting No, no narrowing conversion errors So you keep doing costs and That's at the very least that's annoying Okay, so What does D do about that? Well Not a lot, but it does have one sort of exit Which is this? D keeps something called value range propagation. So if we take and If we try to write the same program In D it would look something like this and we're doing return a plus b divided by 2 and That program actually compiles and the reason it compiles is because the reason it compiles is because The compiler says okay a is now some value between 0 and 255 and B some value between 0 into 255 so a plus B is some value between 0 and 510 And now when divided by 2 the value has to be between 0 and 254 and That fits within a byte So the language lets it through without an error so What we have here is that the language keeps track of the value range of the expression and In some cases it will allow it through but it won't in if I do for example a program called That will not compile I can actually show it to you So it complains about line eight, but it did not complain about line four, so Okay, I think no talk about new programming language can ignore the cool new kids on the block So what does rust do? Well, the answer is rust I hope there are no too huge a rest a Enthusiast in the crowd because I think that pretty typically rust what it does here is Completely solve the problem while completely ignoring it What rust does is it says? No implicit casts of any type between integer types even if it's obviously clear That the the cast would be safe So if we're trying to write the same program in rust What would we have is something like this? so a is unsigned you 8-bit integer and B is an unsigned it integer and we're returning an unsigned 8-bit integer and What we would do is something like this and On its own what this would return is A runtime error because we do add with overflow so the addition of 254 and 252 overflows Which is you know, it's not a bad thing but if you want to fix this then things get a little verbose Because what you have to do is you have to cost a to a 16-bit integer But then you have to cost B into an 16-bit integer as well because there is no implicit promotion and Then you have to cost the result Back to 8-bit and that works but doing three costs in a row just to get the answer is a little too much for my tastes and What's more important than that is that there these are a lot of explicit costs and I hate explicit costs and I'll try to explain why This is the way you cast you do an explicit cast in C you do The type in brackets followed by the value This is how you do the same cast in C plus plus and in D and in rust Can anyone tell me what all of those have in common I'll count down to 10 seconds to let you the leg What all of those have in common is that the only specify the destination type so You're saying what you're casting to but you're not saying what you're casting from and coupled with the fact that a cast is a very destructive operation it Chops of beats it does whatever it can in order to turn the input value type into the output that value type This means that too many casts are not a good thing and the fact that rust makes you cast every time you do anything Is Means that you sort of get used to casting all the time and that I think is bad Which brings me to you know this I've been talking for 20 minutes now and I'm talking about practical so Okay, obviously this question is what's your solution and The practical solution is sort of rethinking of the entire problem and and basically What I'm come at what have come up with is a solution that I'm very happy with I Think it works great, but it's not simple as in to lay out So the simple answer is this You write the code if it compiles you're fine. Not a problem. If there's a problem. It won't compile Breaking this down This comes down to four basic rules, which is this first of all No implicit narrowing, but mind you I'm not talking about implicit type narrowing or big narrowing So I'm not asking whether I'm chopping off beats from the value. I'm talking about value narrowing I'm asking whether the new type. I'm casting to I'm implicitly casting to has less can hold all of the values That the source type can hold Now to make things clearer. I'm talking about the actual expression. So I Am using VRP So I won't be Casting signed 8-bit value to an unsigned value instant type of any on any size It doesn't matter how big it is because it won't have place for the For the negative values But if the VRP tells me that this value this expression will not be negative then it's okay to cast it the second is I'm not promoting. So if if I'm adding to 8-bit integers I'm doing it as an 8-bit arithmetic and if it overflows it overflows in Practical like in C++ and C signed overflow is undefined behavior, but unsigned overflow is fine And and that's where things begin to become interesting If the operation if the binary operation is between two types two different types The compiler will try to find a common type that will accommodate both It says here minimal. That's not set in stone at this point in the language development VRP value range propagation. It means that the compiler tries to keep track of What what what the possible values at runtime might be there so I'm trying to find two types that will hold the the The values of both arguments and find a common ground Which means that I just got rid of the dreaded sign unsigned comparison error Because the compiler will promote if you you try to compare an unsigned 8-bit value to a signed 8-bit value The compiler will promote both to a signed 16-bit value and compare them as such So there's no truncating on the of the ranges and no no case where the the the result of the comparison can erroneously be determined at right at compile time and And the last the last is I'm using expected result type and I want to explain what that is When you when I'm not automatically promoting I might be find myself at a problem if for example I have a function Let's let's look at the same one. So I get a which is 8 bits and be which is 8 bits And I'm returning a 16-bit integer now If I just do this then all of the languages you've seen so far what they would do is they Assuming no promotion is they will add the two as 8 bits There will be truncation. There will be overflow and then after the addition They will promote the result to 16-bit and you will get the wrong answer So for all of the languages you've seen so far to in order for this to work properly let's say it's not 8-bit but 32 bits so that the automatic promotion doesn't save us and this is 64 bits You'd need to cast a or b or in the case of rust both to 64-bit in order for this to work properly Not in practical in practical It starts sort of propagates from the end result the compiler says this at the end of the day This needs to be assigned an unsigned 64-bit value and it propagates this down and then Because of the propagation it it converts a and b at the origin 264 bit value and only then Does the addition? If we look at the If we look at how that looks what we're seeing is that this is the function after name mangling and and What we see is that the first argument is called? Zero and the second argument is called one and then Below them and we do zero extend to both arguments and Only then add them together So with So with no Casts at all this does the right thing so Where this where does this not work? This doesn't work at exactly the the example We started out with since our return type here is an 8-bit integer If we now do a plus b divided by 2 For 252 and 254 this will bring the wrong result In order to Accommodate that Practical introduces a new type of cast which I name I Name the safe cast Which looks like this? it says expect type and The beauty of this cast first of all, let's see how this looks What you do is? expect use 16 encompassing the entire expression and What this does is this uses only implicit casts and therefore it it does not suffer from the same problems that all casts Suffer from because it doesn't do unsafe conversions it will not never unless the compiler can convince itself that narrowing is safe and It will perform the entire Calculation at 16-bit and then once it's done The entire expression including the expect needs to be downcasted to 8-bit But because of value range propagation it knows that the value fits and therefore I don't need in a special cast here So depending on how you want to account this this is either counts as one cast or zero casts So that's the basically the topic. I want to I wanted to Live some space for questions. I will mention that once I Found I figured out the expected result trick this opened the the path to To something that I did not expect when I started out which is return type overloading So I can in practical I can take two functions so Which in this case accept no arguments at all and One returns an 8-bit integer and say it returns 17 and another Returns a 16-bit integer and Say it returns 42 and The compiler can tell them apart based on their return address return type Because the expected type propagates and if there's a context for example, if you just do and you just call fun So obviously that's Ambiguous you can unambiguate it by using the same expect cast from before To choose the overload you're interested So that was one interesting side effect of of Allowing of propagating the expected return type Okay, time for questions Thank you very much for the talk so we are We have a chance now for questions as Indicated Anyone guess not or maybe people are still Quickly typing or something. Yes, someone is typing We have to wait a minute for for people to catch up Okay, so we have one question. Why we don't don't I suggest this feature for the next C++? I actually tried to to suggest something I came from work. I worked for four years I programmed in Dean as as my my main day job and And The problem with integers was was really really big for me and I tried working at D which is a younger language than C++ and presumably more more Flexible and I couldn't get it in and once I started my own programming language and try to figure it out The rules for what has to do. It's pretty clear that these rules cannot be retrofitted into a language It changes too much It's something that you you you can do with it when you start over from scratch But not when you're you have a legacy code. You have to support Okay, thank you very much for that answer Okay, so there's a couple of questions also on the pad let's see maybe let's start with just one more on IRC That's practical have macros like rust I'm Unlike the rest of the languages my knowledge of rust is more superficial If I understand macros correctly, they're An extension of compile time modification of the of the functions And if that's the case then not only right now practical doesn't have it It's a very young language, but not only is that in the road plan in fact the idea was to take these compile time execution into compile time manipulation of code and Carry it over to To where I think it should be I think that we can actually do better than D So I'm not sure it answers the question because I don't hear rust well enough, but I hope it does Okay, thank you Another question is Ada has some similar type conversion restrictions. Do you know about them? No, I'm not familiar with other But I think the conversion restrictions are not the issue here because we saw that rust solve the conversion restrictions completely it has no no Unintended operation, but it just solved it in a way that creates other set of problems I think the question is not just the conversion restrictions, but generally How you solve them and how you make the programming work? Okay, thanks Next question is is practical available as a language specification or even a compiler anyway Or it's a theoretical academic exercise. No, no, no, you just saw it being compiled There's a source force project at github.com slash practical It's a work in progress. It's not yet ready for general purpose programming. In fact, I still don't have Printing capabilities, but I'm working on it because I've I've had to reinvent things that are fairly basic it's a bit slow going but It's actually shaping out. It's already at the point where It's mature enough to have its own syntax file in Vim if you download the the project Which is you know, everything we need that we don't need anything else but seriously it's it's getting to the point where you can actually write programs in it and It's open source. You're free to use it the specs are at specs dot practical PL dot org I've got the I've got the URL for the language right here So you can start here if you want to get it and obviously it's open source All right, thank you We've also got another question while casting is indeed annoying in C. I found that the biggest issue is probably comparing signed with unsigned There are even some CV is issued because of that There's no such problem when doing assembly the correct way But C++ hides the C and C++ hides this problem. Do you have any thoughts on that? Well, it's it's not an accurate description the problem with signed unsigned the comparison in C and C++ indeed is Pretty horrible in that front because it doesn't even warn is that You get a type that is essentially It now has a negative value But because of the type promotion C does it turns into an unsigned type So it now turns into a very big value and then you do something like compare it to zero and The compiler at compile time it doesn't get to the assembly level says well It's an unsigned value and you're comparing it to zero. So It's always true and and it in fact it compiles away the condition So the problem with sign unsigned comparison is a real serious one that people sometimes underestimate and it has nothing to do with assembler and like I said practical doesn't suffer this problem because it will promote to Communal type that can actually hold all the values So it will not have a problem with signed an unsigned comparison It will either compile and do the right thing or not compile and tell you it can't promote Okay, thanks Our next question Is my understanding of the rule system is that one there are no narrowing casts We determine widening cast from explicit result types and permit intermediate widening using expect annotations with support for VRP is this description well It's a bit simplified first of all the finding a communal The part about finding communal types is actually a simplification What happens is that the internal Implementation of of the binary operators is exactly identical to the way overloading works So the power I have when I write the compiler is the same power you will have you have when you write functions to for overloading and essentially what happens is that There are implicit cast defined and Later down the road you'll be able to define your own and each of those have a weight associated with it so the compiler seeks for a cast chain that allows it to fit all of the arguments into the function argument parameters and that requires that if you define the overloads correctly that requires that they match that they use Compatible types And the expected type propagation basically works by the compiler preferring overload resolution I've been doing overload resolution it prefers to match the return type over matching the argument types so it first matches the Precisely the the return type and it only goes on to what to to see which which arguments match best If there is more than one match of the return type So it's a bit more complicated than that, but like I said the bottom line is it either compiles and works or It Gives a compilation error All right. Thank you. So we are almost out of time. Perhaps we'll just Give one last question. So do you have any comments on the rest of the language? For example the interoperability Of practical with the rest of the ecosystem I'm a bit too early at this point to to have any concrete something to to demonstrate, but obviously at the very least no modern language can leave without being in interoperable with the sea and Down the road I would like to have interoperability with the C++ as well and with the compile time execution I'm hoping that will be able to do without offline conversion so The dream is to be able to to put to take a C include file and just sort of include it in practical and have Compile time execution that converts it from a C include to practical include And use it natively All right, thank you so thank you so much for the insightful talk Even as you can see from RC. There's a lot of interest that has been generated I will hang about the IRC now and answer questions there Perfect. Thanks very much. There are a couple of questions also remaining on the pad. Maybe I will try to Join the IRC and I will try to address them or I'll try to answer them on the pad. Thank you very much. Thank you very much