#9 - Understanding Null Safety in Dart - Type Promotions, Null Assertion, Late, Required

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what is going on everyone i'm wicked welcome back to my dart from novice to expert complete course in this tutorial we're going to learn and understand one of the most important new concepts of dart language and that is sound null safety buckle up and grab some popcorn as this is going to be a long but really really important tutorial so without further ado let's get right into it beforehand though i want to send a token of appreciation to everyone supporting me as official youtube members especially to diana and michael aka aza on discord if you want to become a member all you have to do is to click the join button right next to my channel and pick your desired membership having that said let's continue with our tutorial okay so in order to understand null safety we first need to understand what was wrong with the dart language in the first place prior to the addition of this new feature let's say you've developed a car rental application inside of which each user must input a favorite car they'll be gifted to drive one day of the year however john doesn't really have a favorite car he'd love to drive any possible car he has a chance to at runtime the app needs to call the rent car method on each user's favorite car then when dart tries to rent john's favorite car the entire program crashes because john's favorite car is no during the development phase a crash may not seem like such a big deal for you since you can fix it right away right but what if this crash happens during the production phase since dart is the underlying language of flutter if this error happens inside your flutter app the entire application will crash an android or ios application used by millions of users will start crashing constantly fixing an application on both platforms would have to undergo serious time consuming steps like debugging it finding where the error comes from fixing it building the app again uploading a fix for it waiting for the store to approve it and not only that but don't forget that in the meantime users will still experience app crashes and they'll start to rate your app with dozens of one star reviews therefore your entire developer reputation on these stores will drastically drop and that's because of one little variable holding null where it wasn't supposed to you might say that the above behavior is completely unsafe and that these kind of errors should actually be signaled by our trusty static analyzer and not at runtime this is exactly why the sound null safety feature was introduced in dart null safety means that the language is now configured in a way aiding towards not having null values where you don't expect them even more than that if you're developing a null safety dart app and you won't use any explicitly unsafe features it will never throw a null reference error at runtime and that's a fact as we previously saw in the first tutorial when we talked about dart's sound type system in the context of no safety soundness means that if an expression has a static type that does not permit null then no possible execution of that expression can ever evaluate to null on short if the type system determines that something isn't null then that thing can never be no end of the story this brings not only fewer bugs but smaller binaries and faster execution times imagine that the compiler won't have to take into consideration the null case of a variable or field anymore since in a non-nullable type there is no such thing as a null value so let's start having a list of the amazing features coming with dart null safety we'll keep coming back to this list during this tutorial in order to keep track of changes when you opt into null safety types in your code are not nullable by default meaning that variables can't contain null unless you say they can and with null safety your runtime null errors turn into at this time analysis errors and this is actually a huge plus since the faster you observe the problem the faster you'll be able to solve it and you can't actually observe an issue faster than the moment you're coding it what's also worth mentioning is that null safety's goal is not to eliminate the null from the accusation there's absolutely nothing wrong with no no will still exist in any of the dark programs you'll build from now on because it can greatly highlight perhaps the absence of a value you must remember here that the issue is not null itself but rather having null where you don't expect it therefore in style safety the goal is to have control into where how and when null can flow through your program we'll talk about the flow later on but first and foremost let's take a look at the differences in dart's type system before and after null safety null safety begins in static type system because everything else rests upon that now i know i haven't properly introduced you into the syntax of dart language yet with all of its bells and whistles but what you need to know since it is an object-oriented programming language is that everything in dart is mainly an object these objects are instances of different classes dart has a wall universe of types and these types are also classes from which objects can be instantiated from before non-safety the top class in the hierarchy was named object and from this we can see other subclasses or should i say subtypes like iterable and num what's worth mentioning is that no the way we know it is an instance of the null class which before anal safety sets right at the bottom of this tree what we can deduce from this is that every object above could actually hold a null value and thus we find ourselves in the initial error prone scenario imagine that each type here since it is actually a class has its own specific methods you can call their objects with take a num class for example it can call the plus method to add two num objects altogether since num can also hold a null value one of the num objects can also hold null but the thing is that the plus operation is still legal from the compiler's perspective at runtime though the program will throw a no such method exception since the null object doesn't really know the plus method it was called with ending up in a crash note safety eliminates this problem by changing the type here key the null type still exists but it's no longer a subtype of all types instead the type hierarchy looks exactly like the one on the screen right now since null is no longer a subtype no type except the special null class permits the value of no what we can deduce from this is something really really important all types in null save dart are non-nullable by default for example if you have a variable of type num it will always contain a num value if you have a list of integers it will always contain integer values if you have a variable of type string then that variable will only contain a string value nothing more nothing less you might imagine this change actually fixed all possible errors caused by no what a more efficient way of getting rid of the errors caused by no rather than isolating null from the equation right indeed but as i said before the goal here was not to remove null remember null has its usefulness so dart needed to come up with an idea on how to integrate it with the new changes take the following function for example a two-seater can be driven well obviously at least by a driver which can also take a passenger abroad for a cruise this passenger is an optional field therefore if there aren't any passengers willing to go on a ride the parameter can be left no in a null safety environment however we need to mark the passenger field as being a renewable type and we can do that by placing the question mark at the end of the underlying base type what you need to know is that under the hood string question mark is actually a shorthand for a special string or null type and now getting back to the list of changes null safety comes with we need to add two more fields to it as we saw a couple of moments ago in a node safety environment types are non-nullable by default and made nullable by adding the question mark another important change is that implicit downcast got removed implicit downcast were this feature in which dart could downcast an object from a higher level in the tree to a lower level automatically when needed for example if you pass a value of type object to a function expecting a string before null safety to maintain soundness the compiler silently inserted an as stringcast on the argument now since that object could be nullable right now the cast to a string could fail in case it holds no and throw an exception at runtime this is why implicit downcasts were completely removed currently in order to downcast an object the only way to do it is explicitly by using the as keyword so right now in the no save dart environment we can see the universe of types as being split into two hulls the non-nullable types which are those types that let you access all of the interesting methods but can never ever contain null and the nullable types those permit no but you can't really do much with them this puts to light another important thing to be noted about nullable types besides the ability to annotate that they can also be of null type instead of their base type they are pretty much useless please don't make the mistake of thinking of nullable types as being non-nullable plus the extra feature of accepting null values as the properties of non-nullable types can't be accessed inside the nullable types take type specific methods for example inside of the double type you can call the plus operator between two doubles however in the double question mark type you won't be able to do that because it's not safe what if it holds null for example dart won't allow this to happen anymore think of the functions accessible inside a nullable type as being an intersection of those accessible inside the base type and the ones accessible inside the null type you'll see that the only ones resulting from that are the common boring to string equal legal and hash code ones you might say that it seems like nullable types are basically useless they have no important methods and you can't get away from them so why do we still use them then well there are a bunch of dart features aiding towards moving values from the nullable health or to the non-nullable side so that we can entirely benefit from the null safety environment but we'll get to those later on this tutorial for now let's focus on summing up this section regarding null safety types so in the no safety environment this is the current top bottom tree structure as you can see compared to the pre-null safety environment the null class is not at the bottom part of the tree anymore and the object is also not the one at the top therefore if you want to indicate that you allow a value of any type use object question mark instead of object if you need a bottom type you should use the never type instead of no however you might realize the new button type never can hold no real values i mean what kind of value is at the same time a double an integer and a no so what does it mean for an expression to have a never type it means that expressions can never successfully finish evaluating the purpose is to throw an exception abort or otherwise ensure that the surrounding code expecting the result of the expression never runs take this sample of code for example and you'll understand the purpose of never after we've discussed this it is time to add the main ideas to the list of changes introduced with null safety again and move on to how dart manages to maintain the soundness while coping with all the significant type changes so the purpose of null safety was to guarantee that we can never get a null reference error at runtime unless we ask for it as we previously saw the main change is facilitating this behavior we're getting rid of implicit downcast and removing null from the bottom of the type tree however this is not enough the main remaining places where we can get null to sneak in are when a variable comes to life and when you get out of a function or method so let's take a look at some examples on how dart managed to take care of both issues take this function for example in dart every function or method except void must return something and if you don't use return then it defaults to returning no this is what happens in this case and obviously this is not safe in the context of node safety having this said under null safety you'll get right away a compile error if a function with a non-nullable return type doesn't return a value what you need to know and understand is that the analyzer is pretty smart and it will know the flow path throughout the function and check if every possible path ends up returning the proper value as you can see in this function there are a bunch of paths the execution flow can go through however the analyzer is smart enough to see that each of these if and else branches return a value corresponding to the return type of the function what about the throw argument error right here well we previously saw that this is actually equivalent to returning a never type and as you might remember from the type 3 never is compatible with the string type so everything is alright this analyzer ability of scanning the flow of an application is called flow analysis and will cover it up a couple of minutes later now there's only one thing to be taken care of in order to maintain the soundness characteristic of a null safety environment and that is to talk about how to initialize variables in dart as with many other programming languages top level variables and static fields can be accessed and assigned from anywhere in the program before now safety each and every variable which wasn't initialized would have been by default initialized with no in all safety on the other hand doing so with a variable of a non-nullable type isn't possible since obviously they can't hold null now it wouldn't be a problem if the compiler could analyze the entire program and make sure the variable is assigned to the right type before it gets used but assigning this type of job to an analyzer wouldn't be ideal at all because it would take a ton of time to perform such operation you can imagine that by yourself therefore as a result top level variables and static fields must have an initializer that produces a value of the right type right where they're assigned and note that these rules only apply to non-nullable variables you can always make the type nullable for example and then get the default initialization to null just as before another rule related to this value assignment problem is related to class instance fields similarly to what we've previously discussed non-nullable fields must have a value before you reach the constructor body optional parameters must also have a default value since if you don't pass one then dart fills it with the default value and we all know that the default value is null so consider making the parameter nullable or make sure you always specify a valid non null default value however one thing to be taken in mind is that local variables are a little bit more flexible a non-nullable local variable doesn't need to be initialized as long as it is assigned before it's used compared to the top level aesthetic variables having the analyzer to check if a local variable from inside a function has been initialized to the right type before it's used is a much easier and efficient job to do therefore the analyzer is perfectly capable of doing it so let's get back to our list of changes and write down the important stuff we learned here you might observe that all of these changes kind of make the dart language feel more strict more rigid it's not that permissive anymore right no safety was introduced to make the developing process safer not harder or more rigid this is why dar developers needed to think of some solution that could make all these restrictive rules become more permissive so that the ordinary developer won't notice such a big difference in coding before and after the new safety implementation and the first thing they've done to improve the experience was to enhance the analyzer's ability to scan the running flow of an application in many many several ways this ladies and gentlemen is dart's new control flow analysis there are many things to be discussed in here but i'll try and keep it as straight to the point as possible the following part is related to how the dart analyzer got smarter and will be followed also to some of the scenarios in which it can't really tell if the code is safe or not so we'll need to give him a head take this piece of code for example what do you say will this fail in a null safety environment notice that the object class does not have an is empty method inside of it then you might say it will fail throwing a no such method error however the thing is that this code is perfectly valid and let me tell you why the analyzer is smart enough to take into consideration the return break throw or any other ways that could terminate a function call therefore it will notice that if the if statement will exit the function then the object parameter is not a list then the second statement will be executed only if the parameter is of type list so dar promotes the object parameter to the type of list this line of code is actually equivalent behind the scenes to writing object as list that is empty an important fact to be taken care of is that flow-based type promotions does not apply to fields from inside classes and that's because the static analysis cannot prove that the field's value doesn't change between the point in which you check for null and the point in which you use it in this example your best bet would be to let the analyzer know that the temperature field is indeed never null by using the null assertion operation just like this we'll talk about this operator more in depth in a couple of minutes now in pre null safe dart declaring a final variable and not initializing it would have ended up in an error since that's the purpose of that variable to be immutable from start to finish right however with the new smarter flow analysis under null safety the analysis can tell that a variable is definitely initialized exactly once on every control flow path and that it's not modified nowhere after it got initialized this assures the constraints of a final variable are still maintained we've previously discussed this a couple of minutes ago and that is how we can switch a variable from a new level 1 to a non-nullable one remember that nullable types are mainly useless since they cannot access any of the non-nullable correspondent type methods or properties therefore there must be a way to promote the new level type to its corresponding non-nullable one the new smarter flow analysis does that by using simple null checks take this sample of code for example if we check this nullable type if it's not null then it's kind of logic that the variable is clearly not null therefore dart will automatically promote it to its corresponding non-nullable base type thus being able to access any of its methods and fields without any problems behind the scenes what dart is doing on this line of code is calling arguments as list of string dot join all good until now but what happens with the other side of the story when the analysis isn't smart enough to detect if some variables is safe or not take this sample code for example if you go line by line you'll notice that there's no way the error field can be known when it calls the to uppercase function that is because that line is called when code variable is different than 200. in our case it's 404 and our error field was set to not found in that case right there the analyzer doesn't know how to link the code with the error variable what we know for sure is that the error field is not null when it reaches that section of our code so what happens now how can we tell the analyzer mate i know what i'm doing i know for sure this variable is not null when calling this method so please let me call it well for this case the null assertion operator was introduced and you can use it by prefixing the call with the exclamation mark just like this this action is called casting away nullability and is equivalent to writing error as string dot to uppercase of course like any cast using exclamation mark comes with a possibility of failure at run time and it may throw an exception so make sure you're 100 sure when you use the null assertion operator another example when the analyzer cannot prove the safety of code is as we previously saw around top level variables and fields take this example as a reference we've previously learned that this code will end up with an error since by default in a null safety environment dart tells us that a top level variable must be set to a safe value right before the body constructor is called we as humans know for sure that the speed field even though it is presumably initialized as null when instantiating the class will change accordingly to correct values after calling the accelerate and break methods and that the steer method won't call the speed field on nothing therefore there's nothing unsafe with this code however it is not allowed by the analyzer as we previously saw what we could do is make the field nullable and whenever we call the steer method we shall use the null assertion operator to the know that indeed it's never null when we reach that line of code this works fine but the main thing here is that in our context the speed value isn't really meant to be ever known i mean have you ever seen a car running at a speed of no value no it always has some kind of a speed whether it's 0 5 10 15 100 miles per hour it's never no therefore dart has to have a way to let us tell the analyzer well mate i will initialize this variable for sure later on and there will be nothing unsafe doing so to handle the common pattern of state with delayed initialization dart has a new modifier called late and you can simply use it just like this in this case since the field is not definitely initialized every time the field is read a runtime check is inserted to make sure it has been assigned a value if it hasn't an exception is thrown so again this can become quite dangerous if you aren't 100 sure the field will get initialized before it's used the late modifier comes packed with even more features worth taking a look at right now compared to the previous case you can use late on a field that also has an initializer surprising you might say when you do this the initializer becomes lazy instead of running it as soon as the instance is created it is run lazily the first time the field is accessed this might actually become in handy when the initialization of an expression is costly you can combine late with final unlike normal final fields you don't have to initialize the field in its declaration or in the constructor initialization list as we previously saw you can assign it later at runtime but since it's still a final field you can only assign it once now moving on we previously established that in a null safety universe the type checker requires all optional parameters to either have a nullable type or a default value what we'll learn mostly in depth in future tutorials is that dart accepts four types of function parameters positional mandatory positional optional named mandatory and named optional what if you want to have a named parameter with a non-nullable type and no default value you want a parameter that is named but not optional you can do that by placing the required keyword before the parameter and there are certainly more important changes coming up with null safety but i prefer to integrate them as we start taking a tour on dart language therefore as we're most arriving at the end of this tutorial let's sum up everything and complete the list of changes we noticed down the road what i want you to do now is to glance over this list and if something isn't clear make sure to go back and re-watch the corresponding in-depth explanation on that specific topic and with that said it's time to end this amazingly long tutorial hoping that you understood this huge topic of no safety in dart i'm sure this can be really hard to grasp at first and one thing i can advise you to do is to watch it at least twice power up the dart pad editor and try each of the rules by yourself it would definitely be more efficient as a learning curve it is finally time to enter the universe of dark language from the next tutorial we'll start a language tour of dart talking about all of its coding particularities like variables functions classes objects keywords and so on and so forth as always if you like this tutorial don't forget to smash that like button subscribe to my channel and share the video with all of your friends and colleagues in pursuit of top tier development until next time take care wicked is out bye bye
Info
Channel: Flutterly
Views: 2,490
Rating: undefined out of 5
Keywords: dart, dart lang, dart language, dart tutorial, tutorial in dart, dart null safety, null safety, dart null, type promotion, null assertion, late, required, introduction to null safety, dart 2.12.0, null safe, null safe dart
Id: ZZ4VVlggIVk
Channel Id: undefined
Length: 27min 21sec (1641 seconds)
Published: Mon Jun 21 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.