Why null safety? | Session

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hi i'm bob nystrom a software engineer on the dart programming language we recently shipped dart 212 inside flutter 2. i'm going to talk about the most important language feature in that release sound null safety i'll go into some of the deeper reasoning behind how and why we added null safety to dart but first what is null safety if you've been programming any amount of time you've probably had a program crash because you tried to call a method on a variable that you didn't realize was null so null safety enables the compiler to help you find and fix those bugs before you run your code now the goal of null safety is not eliminating null the absence of data is like part of life right some people don't have middle names some treasure chests don't contain any loot null is a great way to model data you don't have right developers are already familiar with the concept it's got a nice short literal form and it's the first class value you can pass around so null isn't the problem it's calling methods on null that leads to pain so what null safety does is partition your variables into nullable and non-nullable ones by default variables are non-nullable you get a compile error if you forget to initialize one or try to assign null to it for example here the compiler won't let us pass null to is only white space which requires a non-nullable string but when you want to allow data to be absent you can make a variable nullable by adding a question mark after the type like the parameter here this lets you pass in null but in return you can't call any methods on the variable that's why we get a compile error on the call to trim you have to get the value into a non-nullable location before you can use it so i think of null as sort of like a toddler toddlers can do surprisingly many useful things but if you let one run around your house without supervision they will destroy something so null safety and dart helps you track where your null toddler is trying to escape to now before i get into why we added null safety i want to put it in context programming is hard when we translate our intentions into code we don't always get it right this is where the the classic iteration cycle comes in you have something you want the computer to do maybe it's a new app or just a tweak to an existing one you tap away on your keyboard for a bit and some code comes out the compiler does its magic on the code and eventually gives you some kind of executable in return you run the program and click around to get it into the state where you can test your change then you try out your change and see if it does what you want click some button to see if the right lengthy lights turn on did it work on a good day yes otherwise you go back to your editor make some more changes and start the whole process over again this is basically our lives as developers i mean presumably we take breaks and get outside but our productivity is dependent on how fast we can crank through that cycle so one of the best features of flutter is hot reload and this is why it's so valuable hot reload only recompiles the part of your program that changed so that build step is in the tens of milliseconds stateful hot reload preserves your app state so you don't need to restart the app and get it back to the point where you can see your change but when i'm programming i want feedback even faster let's say i'm trying to write some code to get the unicode code point of the first character in a string now this code doesn't do what i intend in a dynamically typed language i might have to run my program and get an exception before i realize the mistake but in dart the type checker immediately tells me there's something fishy here the index operator on string returns the character as a string not a numeric code point and since the type checker is always running in the background i get that feedback instantly in my ide i don't have to run a compiler or load the program this is about as fast of an iteration cycle as you can get and this instant feedback is why i love static types i think of the type checker as like the hottest reload okay so the type checker is great and using it to catch bugs is awesome can we just catch all the bugs with the type checker in practice uh no like everything in life there are trade-offs you can certainly make your language worse by way of the type system in the original pascal an array's length is part of its type that sounds nice because you get some compile-time bounce checking uh but strings your character arrays in pascal and that means if you wanted to write a string manipulation function you had to decide which string size it worked with if your app had a string of some other size you couldn't call the function it's basically impossible to write reusable string code this is why we program in c now instead of pascal and i mean deal with constant security vulnerabilities from string overflows so maybe pascal had it right after all but it's also dead which is telling us something when you add a runtime feature to a programming language you increase the set of things users can do if you add say threads now users can do concurrency type system features don't work that way the user experience for the type system is mostly compile errors which prevent you from running your program making the type system more precise adds new things users can't do now these additional constraints are valuable when the things that prevents you from doing are not what you want good compilers stop you from accidentally shipping bugs the type system is sort of like guard rails guard rails are great when you're standing on a cliff but you don't want them you know cutting across your front yard when you're just trying to get your mail so before we get adding safety to the type system we had to work through whether it would be more of a help to users than a hindrance so here's what that analysis looks like the first question is does the static analysis provide real user benefit leif peterson's the main designer of null safety and he often says we can make the type system dance to whatever tune we want we could probably make the type system track which numbers are even or odd but that wouldn't help you make good apps right so null checking though is pretty compelling we know that null reference exceptions are a common cause of crashes in shipped applications we know developers waste a lot of time tracking down these bugs the easiest way to know that this is valuable is the users keep telling us null safety has been the number one user request literally since the day dart was announced we know it has value but does the value outweigh the cost by cost here i mean effort to learn the feature and effort to use it while programming users would probably like it if the type system could prevent division by zero errors right but doing so requires surprisingly complex type system stuff you're talking full dependent types with each function that touches numbers you have to write a little arithmetic proof to appease the type checker that's a lot of effort for a runtime error that's fairly rare in practice the cost would outweigh the benefit fortunately null safety is in a much better spot several other languages have similar features so many developers have already spent the time loading it into their heads and we get that knowledge for free when they come to dart and the type system complexity for nullable types is actually pretty straightforward they're built on a formalism called union types that's been understood for years supporting union types in their full generality along with intersection types does get pretty intricate but the limited subset that we need for nullable types is simple and pretty intuitive when you're programming you basically just need to decide which variables you want to permit null you sprinkle a couple of question marks in and you're good so it's not too complex to learn or too much work to use so we're looking pretty good but there's one last challenge when it comes to type systems static analysis is conservative it looks at every single way your code could execute every control flow path that could be taken every value a variable might have and if any of those have a problem you get a fatal compile error it does an extremely pessimistic analysis now that's great for ensuring bad code doesn't sneak through but it comes at a cost of being uncharitable towards good code there are many totally safe programs that at runtime will never go wrong but the type system isn't smart enough to figure that out here's an example of what i mean so in this code here when the argument list isn't empty we initialize a border variable with a string then later again if the argument list isn't empty we use that variable now we know the argument list doesn't change and will only ever use border when it's been initialized but the compiler has no way of knowing that those two calls to is not empty always return the same value so the fundamental trade-off with static analysis is that it blocks bugs but it also blocks some number of dynamically correct programs like this one and that frustrates users they know their program is right but they can't convince the type checker now you can reduce this irritation by making the type system more expressive so that programmers can more precisely explain to the type checker what their code is doing but my experience is that every incremental bet of expressiveness you add to a type system adds a lot of complexity you start off with relatively simple stuff like subtyping that lets you write a lot of programs but you lose too much static safety when working with collections so you add generic types you want to support higher order methods on those collections like map and where so you add generic methods and then some user asks why you can't pass a list of string to a function that takes a list of object fixing that gets real hard real fast you need something like wild cards in java variants annotations in c-sharp and scala or type projections in kotlin even if your language does support one or more of those you still have to teach users how to use them you find yourself on stack overflow explaining how a parameter inside a callback is actually in a covariant position because a contravariant position inside a contour variant position flips back to being covariant and sometimes you wonder if static types are worth it at all so what about null safety how gnarly does it get can we design a fairly simple type system that correctly allows most dynamically safe code now this is honestly an open question for me when we first started working on the feature i wasn't sure whether null safety would actually be usable fortunately we had a really helpful feature to build on dart has long had a feature called type promotion if you do a type test on a variable in is expression inside an if condition then inside the body of the if statement dart promotes the variable to have that more specific type that way you can call methods defined on the subclass even though the variable's declared type as a super class what's going on here is something called control flow analysis the type system looks at all the ways that execution can flow through the program if a piece of code can only be reached by passing through some successful type test then it can prove that the value in the variable must have that type kotlin has something similar called smart cast and like they do we extended dart's type promotion system to also look at null checks when you look to see if a variable is not null then dart promotes the variable to a non-nullable type which then lets you call methods on it crucially this is what dynamically correct dart code already looks like in other words unlike you know dependent types or variants you don't have to write separate type annotation junk to prove that your code is using a nullable variable correctly the control flow and null checks you already have are that proof and with this we found that about 90 of existing dart code is already perfectly null safe when opted into the new type system this even means code that is using null which makes sense when you think about it most of that code was dynamically correct it's not like we write tons of code that you know crashes on null reference errors and we just committed leave it around right we fix the code by checking for null using control flow so control flow analysis enables the type system to just understand those dynamic checks of course ninety percent isn't a hundred percent and there are some places where the static analysis can't see that your code is safe in particular fields and top level variables don't play nice with control flow analysis so in addition to the type system changes we added a handful of other features to sort of ease working with null first there's a little non-null assertion bang operator for places where you just want to forcibly tell the type checker you know something won't be null it's um it's like an explicit downcast using as my favorite is the late modifier this defers uninitialized variable checks until runtime for example here we don't initialize the color field when the home icon state is first constructed now we know that this code is safe because flutter calls a nit state before it calls build so color will be initialized before it's used but the type system doesn't know that it would normally give us an uninitialized field error using late on that field silences that error it's similar to late in it in kotlin but late fields in dark can also have initializers which get run lazily like the lazy modifier and swift so it's one little keyword but it covers a bunch of patterns there are a couple of other tweaks to the language like require name parameters and it's this entire suite of features that work together to make null both safe and pleasant to work with there is another trick you can use if your type system is getting in the way of code that you know works you can poke holes in it many type systems are deliberately unsound an expression with some static type produces a value of another type at runtime and the program just keeps going for example typescript is intentionally unsound around function types that makes it easier to take all the world's dynamically typed javascript and get it migrated into typescript kotlin is also unsound with respect to null because null can flow in through java interop now with dart we ship our own compiler and runtime and core libraries we also have a lot of analysis infrastructure we can use to build automated migration tools we've got the whole stack that gave us the ability to not just add null safety to dart but to design a sound set of type rules that means that an expression whose type is non-nullable can never produce null that level of safety is nice for your peace of mind but it's more than just that when you have a static proof of some property of the user's program that gives the compiler a lever that it can use for optimization before null safety our compiler had to omit null checks before each method call just like compilers for other languages with nullable references do those checks make your executable larger and slower with sound null safety the compiler can discard all those checks when the expression static type is non-nullable because it knows null can never reach that point also in some cases we can store variables directly in registers or on the stack instead of having to heap allocate them you get a faster app that uses less memory often the way to make developers more productive is to give them higher level language features you know stuff like garbage collection or higher order functions for transforming collections but programming at a high level usually comes at the cost of slower runtime performance now with sound and all safety and dart we believe the language is more productive but your app also gets smaller and faster it's a really rare win in language design now that's a good sales pitch for your boss but what i really care about is what it feels like i find working in a big code base to be overwhelming it's a struggle to simultaneously hold in my head all the various states the program can be in and you know figure out which ones might let a null sneak through i've migrated a lot of code to null safety in the past few months and it's it's such a relief to unload that null tracking out of my head instead i actually see where null can go right in the text of the program that leaves me with a little more brain power to focus on what i'm trying to do and i really hope it gives you that same feeling too now if this talk hasn't satisfied your curiosity we have a lot more docs on null safety you can check out at the links here and also there are a lot more flutter talks at i o this year so thank you for watching you
Info
Channel: Flutter
Views: 25,748
Rating: 4.9693875 out of 5
Keywords: purpose: Educate, type: Conference Talk (Full production), pr_pr: Google I/O, null safety, why null safety, what is null safety, null safe, null-safe, nullsafe, null safety basics, null safety explained, migrate to null safety, null safety in flutter, dart null safety, flutter enable null safety, benefits of null safety, Flutter developers, Flutter development, #GoogleIO, Google I/O, Google IO, Google developer conference, Google announcement, Google conference, Google
Id: tP9TcrUZoIs
Channel Id: undefined
Length: 15min 14sec (914 seconds)
Published: Wed May 19 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.