Be Careful With Return Types In TypeScript

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
turns out there's a lot of opinions on how to type your functions in typescript and it turns out a lot of those opinions are wrong this convo has been fun but it's time to end it let's talk about why you should avoid return types when you're able to in this example we have a result type the result is the thing we get back from this function a result has a status which is either OK or error and it has a value or an error if the status is error and this contrived inferred example we're not passing a return type which means this type isn't being used and the result is that the type that we get back has a value that is either string or undefined even if we check and confirm status is okay what we're getting back here is this weird Tuple of status during values during error undefined or status string error error value undefined and that's not a great representation of the types going on here if we give this a strict type by putting a type definition at the end of the function def like I have here then we'll see with this contrived example we now know confidently that value is string after confirming the status is okay this seems like a pretty obvious compelling example of when you would would want to use return types right told you we could do better because I think we can this next example is using as const because in this example there is a set of values that are and aren't valid and as const basically tells typescript don't treat the things in here as strings treat them as constants you'll notice we get something even more strict that's interesting we get back yay why are we getting back yay here well the only two things this can actually return are status OK value yay or status error error error because of that the true type of this function is status okay value yay or status error error new error and when you use as const with inference without the type definition you're guaranteed the correct result like the exact correct result the actual truth of what's being done and being returned funny enough if you add back the return type on here it actually overrides the truth with a more vague result and you'll see here in space is far enough now the result now is that value is string even though we know value is yay because the as const gets overridden by the thing you put here you all know anything about me you know I love diagrams so obviously I was going to throw this in a diagram I have a key here with the four things that will be visible in any given piece I drew a circle which is all the potential types we could reasonably think result might be it's our goal as developers to narrow that type definition down to its very core of the truth so in this diagram I have the potential as a black circle on the outside and I have the truth as this little green circle on the inside we're trying to figure out how these different solutions relate to the truth if we take the same code the result the values type is EA the raw inference does not give us yay it doesn't even give us string it gives us string or undefined which means the types that it could potentially be are large return types narrow it slightly as we see here because return type knows it has to be a string so in this case the type is more narrowed it's closer to the truth and then we have in the center here cons d a because if you as const these with constant inference the result is the exact truth the exact thing that we're getting back here guaranteed thankfully in this example all of the different options contain the truth which means we're bickering over the scope of what exists outside of the truth here and I understand I can totally see why people would not want this area to exist and if they could avoid it would choose to but what if I told you it wasn't that simple what if I told you return types can lie I think lies are the root of all evil and type systems I prefer vagueness to not telling the truth and with inference you can't lie with return types it's trivial to lie and it's even pretty easy to accidentally let lies slip through code review the first example I have here comes credit Julius one of the lead maintainers and creators of create T3 app Julius made an example that comes close to my heart because I've made mistakes like this before where we have a user type that has a username and email and notably it doesn't have a password because this is meant to be the user that we return to a user when they make a request on our application however if we have a function get user that returns things that it shouldn't like in this case a password this type is not representative of what this function returns and if we don't give this function a type and we call it and we get the response we see the inferred responses or we see that the type that we get back is correct it's username email and password it knows that we have the password here because we returned it so obviously that's going to appear in the typed result sadly it's very easy for strict type returns to override that result so in this case and get user strict we have a return type of user which only has a username and an email so even though we are returning something with a password that we would want to see in our types definition it's not there anymore by putting this type here we have overridden the type of this or we've overridden the type such that password magically vanishes in typescript land even though it's still there that's a lie this is an incorrect type definition if we take a look at the diagram for this it should highlight where the lies are happening so we again have our black circle and the truth within it the type of user.password when we call user is very different depending on which of these Solutions we use to type it the truth of what user.password is here is pass because that is what we're getting back when we call user.password if we use raw inference we're going to get back a string which is fine it lets us know that we have a string there when we don't intend to and that the return or the response of get user has a password in it so it's still correct it just has more vague around what the type is because it's a generic string not the specific string totally fine the return type will actually give you an error here when you try to touch check or interface with user.password it will error because you've told typescript no that doesn't exist even though it does the return type is now giving you an error on data that actually exists and you have all of the things to know that that data exists if you ask constant you're going to get passed because that's what the value is here and it's now been made into a constant but the harsh reality here is that all of these are the truth except for return types because return types here contain some of the truth but are also missing some of it they're lying a lie is when you don't include the truth in your response and that is what I see here when you use return types it becomes much easier to lie and that's a huge concern to me we can negotiate and argue all day around the merits of making your type definition closer to the truth but we also have to acknowledge that return types allow you to move the type further away from the truth and outside of the scope of Truth very scary for my final example I want to show the code that primogen gave an example with on stream yesterday because although it looks good on the stream we later figured out it's entirely broken and super risky so much so that Prime even replied earlier today saying I showed overloading lying which it does and typescript should get rid of that behavior immediately well typescript behaves how typescript behaves and right now return types enable typescript to lie a lot in this example we have a type user which has a role either user or admin we call this get user function I need to get user overrides because this is the one we're overriding it you call it an ID we call it with a role we then return that role ideally we'd probably return other things here like permissions and stuff but the example is meant to be simple where roles user we get back roll user role as admin we get back roll admin the override here is basically telling typescript oh by the way if you pass this you're guaranteed this subset of the type because get user overrides always returns a user and both of these are valid users so we've effectively said here is if the input matches this type then the response has to match this type I don't know how closely you've been looking but that's not the truth here because if roll is user we're not returning role user we're actually returning role admin if you didn't notice that then you would have failed this encode review because guess what the types are lying here if we look at user override the response will get user override with user it thinks the type of is role user but it isn't we have just used the override here to lie to typescript because we told typescript no you don't know better about our types we know better about our types but we don't here we were wrong and a code reviews the only way you're not going to ship this code however if you just let inference do its thing you're going to get a more vague type sure it's not ideal I understand but in this case this more vague type of string or null is the truth and I personally believe truthy code is more valuable than slightly more narrow type definitions if we take a look on the diagram here you'll see why this one's so scary the truth is roll admin and sadly even with the consts we can't narrow it down to here without the possibility of lying the raw inference would be roll string or null just pretty accurate totally fine to operate within the override return type so if you're using overrides for the return type on this it's now going to be roll colon user which is a lie if we ask cost everything we're gonna get back roll user or roll admin or null because it knows strictly what these are that doesn't necessarily know the mapping of when you pass a specific role which thing comes back that all said God override return types just don't exist within the truth at all in this case you'd have to actually modify the code to bring these back to the truth and then all future code modifications have to be cognizant of the fact that this type may not overlap with the truth and you as a developer have to be very very proactive to make sure that happens or way easier just don't give it a type the vast majority of the time if you don't type the response of the function the type you get back is good enough to work with and on the rare occasions it isn't you can as constant move on you really want this function of a specific narrowed down type you better put a massive comment on it letting future developers know that it's easy to lie if they aren't careful once we get into any this becomes way more common but even in these simple use cases where all of the inputs and outputs are strictly typed return types often lie to you so be careful don't use return types if you don't have to I don't think they're 100 evil I think they're closer to like 20 I just wish we would acknowledge that 20 evil and be very very careful about how we introduce evil into our code bases inference always tells the truth even if the truth is more vague return types can lie to you and you should be careful when you use them as good as I am a typescript I relied on much smarter people to help build this stance and also find all of these examples so I'm going to let all of them let you know what they think hey I'm Malta and I prefer inferred return types I'm trash and I generally prefer inferring my return types hey I'm Ben and for strong intuitive safe typing I try to default to inference in typescript hi my name is Josh Goldberg and I generally prefer inferring return types hey I'm Dax and I prefer inferring return types hi I'm Maple Leaf and I prefer inferring return types hey I'm Alex and I almost always in fire types hi there I'm Tanner lensley and a vast majority of the time I use inferred return types hi I'm Ken C Dons and although I don't like hard and fast rules most of the time I infer my return types hi I'm Napo cook I think you should be using inferred return types by default except when you have a function with multiple branches except in library code and except when you have really Niche weird performance concerns with the typescript compiler where a return type solves it but that's it by default use implicit returns hopefully we can end this conversation once and for all if you want to know more about ways people use typescript wrong I'm going to pin a video right there so take a watch of that one should be good peace nerds
Info
Channel: Theo - t3․gg
Views: 61,455
Rating: undefined out of 5
Keywords: web development, full stack, typescript, javascript, react, programming, programmer, theo, t3 stack, t3, t3.gg, t3dotgg
Id: I6V2FkW1ozQ
Channel Id: undefined
Length: 12min 7sec (727 seconds)
Published: Tue Feb 07 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.