 In addition to lists, static pigeon also has what we call arrays, which are very similar conceptually, they're homogenous ordered collections of values. But there are some important subtle distinctions and to make that clear, we're first going to refresh how lists work exactly. So here's an example of using a list of integers. We have a local variable Z of type list of integers. And in the next line, we're performing a set operation, we're trying to set the value within that list that index zero, in this case, to be the value 999. The problem with this line is that at runtime, at this moment, as of yet, Z is still nil. Recall that variables that store lists in pigeon are actually just references. They're storing addresses of lists. The actual list exists somewhere else in memory. And so as of yet, there is no actual list, we just have a variable that can store a reference to a list. And so this operation is invalid. The compiler doesn't know that though, the compiler doesn't presume to know at any moment what is assigned to any variable doesn't presume what the actual values are, it just knows their type. So it's going to accept this line of code and compile, but then runtime we're going to immediately have a problem. We would have the same problem if we tried to do lend Z because Z is still currently nil, you try and perform a lend operation on nil, then it's nothing it doesn't, it can't give you the length of nothing. So that's also a runtime error. But then in the next line, we finally are actually creating a real list of integers. In this case, an empty list of integers. That's what this operation does. Remember the previous slide, we had actual values here so we can like populate the list with initial values. But in this case, we're just creating an empty list of integers. And that's being assigned to Z, meaning there's now this new list of integers somewhere in memory, and its reference, its address is stored in Z. And now the lend Z operation is valid and it's going to give us back zero. And also we can push to Z, add a new values, tagging them on to the end of the list and expanding the size of the list. That's what this does. So on the next line, if we do lend Z again, well now it's one. Looking at arrays now, arrays are also logically their collections of multiple values of the same type stored in a particular order. There's the first thing in the array, the second thing, the third thing, etc. But the first important difference is that for an array, the size of the array, the number of elements it has is integral to the type. So here when we specify an array of integers, we have to also specify that's an array of three integers. And in fact, the value we supply here, it has to be a number literal, you can't put in an expression, you can't have like an operation that returns something because the compiler needs to know at compile time, what kind of array this is so it can't be some value that's produced by running the code. It has to be a so called compile time value. Anyway, so another important difference is that the local variable a here is directly storing an array of three integers. So what that looks like in memory, if you imagine what a looks like, it's however many bytes are required to store a single integer, and then three of those slots right next to each other in memory, it's all contiguous. We're not using a reference pointing to the actual array elsewhere in memory, it's directly the array itself. So we don't then create the actual array, like we do with lists, but with lists, we have to actually create a list, and then assign it to a variable storing a reference to list here the variable itself is the array. And then in the first line of code here, we can use Len on a because a is an array type, and it's actually just fixed a compile time it's known to compile time what the line of a is going to be it's always going to be three. There's not much reason to use Len on arrays, because you can always just look at this type of the array to know what the size is. But stylistically, you might prefer to write the code this way just to be clear, this isn't really a runtime operation when the value of Len is an array. It's known to compile time with the length is anyway, then in the next line, we can use get on the array. And we get the value index zero and then one and two. And all of them are the value zero currently because when you create an array, the initial values sitting in the array are the default for whatever type of rate is so this is an array of integers and the default integer value is zero. So all the values currently are zero sitting in the array. The fourth get operation here is going to be a problem, though, because this is an array of three elements, and the last index of the arrays to three is outside the bounds. And so at runtime, this is going to trigger an error. It's going to import our program. It's a bad operation. You might be wondering, well, if the compiler knows what the size of the array is, why can't it look at this and say that it's invalid. Well, when we do a get operation and we plug a number literal and actual value in written in code, right here in principle, yeah, sure, the compiler could look at this and say that's invalid. But they're imagined instead of three here, I did some kind of called some function foo and it's going to return the value three some index out of bounds, the compiler doesn't know what value any function returns. It can't know like that only is determined at runtime, it depends on what happens at runtime. So it doesn't even bother to check in cases like this, even though in principle in this specific case it could, but it doesn't bother it considers it your responsibility to make sure that when you index into an array, you have to specify valid indexes. So it's not going to catch us a compile time, but it will catch it at runtime because it checks the bounds every time you do a get or set operation and make sure that the indexes inside the is within the proper balance of of your array. The next line though this first set operation, that's fine because index two is within the bounds of our rates last element of the array, and we're sending it to 999. It was zero, as you see here, but now it's going to be 999. And so if we get a two again, now we get back 999. And this though this push operation, this is totally invalid because you can't push to arrays. The size is totally fixed. You can't push to arrays because you can't expand them. So this is just a totally invalid operation on an array. So now what is the point of arrays? Why have them when we already have lists? Well, generally arrays are more efficient because arrays are storing things contiguous in memory. If you look at the details of how the language handles lists and the way you can like add stuff to them, the elements of your list are not necessarily stored and in fact generally not stored contiguously next to each other in memory. And that can create issues if you're trying to write high performance code. So when you look at higher performance languages like CNC plus plus in particular, but then also go which static pigeon is semantically based on. It has arrays because there are cases where you want to make sure you're dealing with data that's not scattered into a bunch of different parts of memory. You want it to all be sitting next to each other in memory because that's much more efficient to access. So that's one big reason also the fact that array variables are directly storing the array value itself. That's one less level of indirection when you deal with lists. The variable itself is just an address pointed to the list elsewhere. And so that's another case where in execution when we access the elements of the list, we have to follow the reference to the list and then find the place within the list. It's that extra step that can really kill performance, which may be important in high performance applications. In our sorts of trivial examples, you're never going to notice the difference in educational examples. In fact, given how powerful modern computers are, you're really not going to notice the difference until you start doing certain kinds of specialized programming. But that is the main rationale for having arrays while also sometimes having lists. On the other hand, you're losing convenience because arrays are fixed in size and sometimes you need something that can logically expand in size. Because in many cases, maybe you're dealing with something where you have some number of things, but you don't know at compile time how many they're going to be. That that is actually a common case. So sometimes you have to have lists or something like them. You can't just have fixed size arrays. But in many cases, you can get away with having fixed sizes arrays. And so for efficiency reasons, they may be preferable. Just to drive home that the size of an array is really part of its type. Looking at this example here, we have main with three local variables X, Y and Z, all arrays of integers. But X is an array of three integers, whereas Y and Z are both arrays of five integers. And again, remember the syntax comma I mentioned briefly in an earlier video, it's just a convenience so that you can spread what otherwise would be a single line onto multiple lines. That's all that's about. It's just a syntax thing. Anyway, so what we see here is that if we try to assign Y to X, that doesn't make any sense because they're both arrays of integers. But Y is an array of five integers and X is an array of three integers. So I'm not even sure what that would mean to try and assign one to the other. And the compiler doesn't either. So it totally rejects it. You can't assign things of the wrong type to a variable. Y is an array of five integers can't assign it to X. This last line, however, is okay because Z and Y are both arrays of five integers. And what happens when you assign one array to another, as you might expect, is it just copies all the respective values. So, I mean, in this example, they both still have their default values of a bunch of zeros. So nothing would really happen here. But imagine that we did populate the values of Z. We set some of its values to something other than zero. Well, then after this assignment, Y would look the same as Z. They'd be two separate arrays with all the same values. Another thing we can do with arrays of the same type is we can use EQ to compare their respective values. So this is taking array X and Y, they're both arrays of integers of size three. And what this does is it compares the first integer of X to the integer of Y and then the second integer of X to the second integer of Y and the third integer of X to the third integer of Y. And if all of those match, if the two arrays have all the same respective values, then EQ here will return true. Otherwise, it would return false. In this particular case, we haven't set any of the values of X and Y. All the values are at their defaults of zero. So, of course, both arrays are going to be equal. This example also illustrates that when you use EQ, it's an operator that's expecting all of the operands to be of the same type. Otherwise, it wouldn't make any sense. You wouldn't want to compare a string to a number for equality because, of course, it's never going to be true. And in a statically typed language like static pigeon, you always know exactly what the type of everything is going to be. So it's obviously a mistake in your part to even attempt to compare totally different type things for equality. So in this case, for example, if one of these arrays was a different size, of course, let's say like an array of floats instead of events. Well, now this would be an invalid equality operation. And the compiler would complain that we're trying to do an equality operation on different type things. And so we couldn't compile our code until we fix this. But as long as they're the same type, which for the case of raise, means same type of values and the same size, then we're good. Another thing we can do with arrays, which is really just a convenience actually, is you can use an array type like an operator to create a array of that type and plug in values for that new array. So this whole operation here is creating a new array with these values first 520 to negative 24 than 10 and returning that. So this value is being assigned to X, which has the appropriate type of also being an array of three integers. But currently, before this assignment X is an array of three zeros, right? But then after this assignment, all of these values are copied respectively to X. And so X is now an array that has these that looks like this, it has 520, negative 24 and 10. Being able to create arrays like this is really just a convenience is not strictly necessary. What we could always do is we could just have our local variable, which is an array, and we could go through and manually set X at index zero is 520, and then set X1, negative 24. I mean, this would logically get the same result, right? But it's just as convenient. Here now is an actual complete program using arrays. And it's an artificial example, but it is all legal and all works. So we have our main function, which has a local variable R, which is an array of tenants. And we want to initialize the values of that array. So the most convenient way to do this is to use a syntax. Technically, this is creating a second array and then assigning all this respective values to R. Behind the scenes, the compiler is smart about this, though, and it doesn't actually create a second array, just assigns these respective values into R. But logically, this is creating a new array, a second array. Anyway, so now R has the values 1234567810. And we're calling our sum function and passing in R. So you can pass arrays as values. And then what's going to happen there in the call to sum is that this parameter nums, which is expecting an array of tenants, just like when we assign an array to an array variable, the whole array is copied. Logically, it's the same thing when you pass an array as argument to a function. So this array is, with all these values, is copied to nums. So nums now has the values 1234567810. Did I skip a number? Oops, why is it? Oops, there should be a nine there. Yeah, obviously. Okay, so nums function, which is declared to return an integer, meaning there has to be a return statement at the end, and it has to be an expression, which is an integer, and that checks out the local variable val is declared to be an integer. So this is all good. And what we're doing is we're iterating through all the integers in nums and accumulating them in the variable val. That's all this is doing. So we use for each to iterate through our array of nums, just like we would with a list, v each time through the loop gets the value from the array. We're adding that to the current value of val. Oh, by the way, here, val, this is different from dynamic pigeon. In dynamic pigeon, again, when you create a local variable, val would have the default value nil. So it wouldn't be a number at all. And so this would be an invalid operation the first time because you can't add a number to nil. That's a bad op. But this is okay because here in static pigeon, it's an integer and the default value of val, its initial value is zero. So we don't have to explicitly say that val is zero. We could put that there and be fine, but we don't need to. So the initial value of val is zero, go through at each value of the array to val that gets accumulated and then we return it. We return the sum of all the numbers. So then this should return 55. So this program will then print out 55 and that's what sum when we pass in this array is going to get us back. Do note that this is one case where arrays can be less convenient than lists because we've defined a function sum that only works for arrays of 10 integers. It doesn't work for arrays of any other size. It's baked into the cake here, then this particular sum function is expecting arrays of 10 integers. That's just fixed. If you want a function that sums together just any number of integers then it has to take in lists, not arrays. So this is a case where dealing with arrays for efficiency also brings in some inflexibility. In addition to arrays, we also have what are called slices and slices in a sense are another kind of collection of ordered items but what a slice actually represents is a pointer, a reference into some index of an array, say like to the first time on the array or the third or some position within the array and then logically it also has a length and a capacity. Effectively then it represents a sub-range, a portion of some existing array. You have an array in memory say with like 10 items well then you can have a slice pointing to say like the second item up through the sixth or say the first item up through the fifth or the first item up through the last you can have a slice representing the entirety of some other array. The slice is not a copy of the array it's just a reference to some position within the array and some elements from that position. That's what a slice represents but given a slice you can then access the elements within the array referenced by the slice. Slice points into some portion of an array perhaps even the whole of the array but then through the slice we can get and set the elements of that portion. So here in main we have again a local variable r which is an array of 10 ints and we have now also s which is a slice of ints notice that we don't have a size slices of say ints are all the same kind of slice no matter the size of any particular slice events they're all slices of ints as far as the compiler is concerned and then on this line we are initializing our array to have these values 10, 20, 30, 40, 50, 60, 70, 80, 90 that should be 90 there let me fix that, okay and then we use a slice operator to create slice values s is being assigned a slice value returned by the slice operator what does the slice value represent? Well it represents a sub portion of the array r from index 3 starting at index 3 up to but not including index 7 and so we can say that the slice has a length of 4 because 7 minus 3 is 4 but then also every slice value has a capacity which in this case is going to be 7 because starting from index 3 of this array if we start at index 3 which is 40 here then up through the end that's 7 elements so the length of the array 10 minus the starting position 3 the starting index 3 that gives us 7 so that's the capacity so now with this slice s representing effectively this portion of the array index 7 is 80 so starting at index 3 40, 5, 50, 60, 70 that's the portion represented by slice s we can access the element at index 0 of s that's actually index 3 of the array so we get back to value 40 and then if we get s1 then we get back 50 this is index 1, the second element of the slice but it's actually logically then the fifth element of the array which the slice is pointing into and so on s2 would be 60, s3 would be 70 and then s4 now that's beyond the bounds of the slice logically our slice has a length of 4 and so effectively the last index is 3 the actual array it's pointing into yes has more values beyond that but it's an illegal operation to access values outside the bounds of the slice logically our slice only has a length of 4 if we use the set operator on a slice then we're actually modifying the array that it points to so here if I set index 0 of the slice s to be negative 999 well the value that was there at index 0 of the slice that's index 3 of the array that was 40 negative 999 and so if I get the value at index 3 of the array that is that particular slot within the array so we get back that value negative 999 so it'd be very clear a slice points into some existing array the array is separate from the slice but when we get and set values through the slice we're actually accessing the array flipping this example if we set a value in the array well that change can be seen in a slice pointing into the array so here if we set index 6 to the array that's position 0, 1, 2, 3, 4, 5, 6 it's a 70 value if we set that now to 1, 2, 3, 4 and then get index 3 of the slice this is also index 3 of the slice because the slice starts here at 40 this is 0, 1, 2, 3 of the slice we get back the value 1, 2, 3, 4 to make this absolutely clear here's the same basic setup we have an array of 10 integers and we also have a variable s which is a slice of integers the array is initialized to have these values 10, 20, 30, 40, 50, 60, 70, 80 and that should be 90, 100 and understand that this variable s initially its default value is it's not referencing any array yet and it has a length of 0 and a capacity of 0 it's not nil, it's not nothing and so we can use len which gets us the length of the slice which is 0 and the cap operator which only works on slices and gets us the capacity of this slice which is currently 0 but then if we use get on the slice well it has a length of 0 so any index is invalid any index is out of bounds and so getting index 0 from the slice will give us a runtime error but then if we use the slice operator on the array with starting index 0 and n index 7 assigning this return slice to s well now s is a slice that points into array r it has a length of 7 it has a capacity of 10 because starting from index 0 there are 10 slots through the end of the array from that point and if we get in value at index 7 we're out of bounds index 6 is the last valid index if I then assign to s a new slice value pointing into the same array and also starting at index 0 going up to but not including index 10 well effectively now we have a slice representing the whole array and so length of s is 10 capacity is again 10 and if I get of the value at index 7 then we get 60, the value at index 9 the last element of the slice and also the array is 100 but then if I try and get the value at index 10 now we're out of bounds oh and by the way I should be absolutely clear here this code is actually not valid the compiler will reject it you can't have these lincap and get operations you can't have a statement where it doesn't have any purpose other than to retrieve a value but then you don't do anything with a value the compiler will look at this and say oh you can't have a standalone get statement that doesn't make any sense that's not a useful thing to do but just for expediency here I just wrote the code this way we can make it proper by printing out the value of each of these operations like I could add print here line operation is a valid sort of thing to do as a standalone statement but this by itself is not a useful thing it's just expediency for my examples one last example to make the slice concept absolutely clear here we have the same basic setup but now we have two variables for slices S and S2 both slices of integers and if we use a slice operator to get a slice of the array from index 4 up to 9 and then assign to S2 the slice of the array from index 8 up to 10 so effectively then S is representing the slots that as you see it here have these values it's these positions of the array and then S2 is these positions so there's one bit of overlap here this slot here of the array they are both within both of these slices but within S that's the last index of the slice it's 4 remember the length of this slice is 5 9 minus 4 and so the last index is 4 that's the slot here but then for the second it looks like this and so index 0 is that same slot so if we get S4 we get 90 if we get S2 0 we get 90 they're different indexes for the different slices but they represent the same slot in the actual underlying array now as a convenience we can use the slice operator to create new slice values that implicitly an array is being created and the operation returns a slice representing that full array so actually what's happening here is that an array of integers with these 5 values in this order is created and then what we get back and it's assigned to S here is a slice referencing that whole array it points to index 0 of that array and then has a length and capacity of 5 so this spares us from having to create a separate array variable and then getting a slice of that array this is just more direct because sometimes you really want to work with slices and not really work with arrays directly but of course when you work with slices underneath there has to be an array of actual storage because that's where the values actually go slices themselves don't really store values logically they represent portions of arrays the arrays store the values another thing we can do with slices is we can use the slice operator so here we get a slice of the array from index 2 to 8 and that's this range of slots within the array but then if we use slice on S the slice value from index 3 to 5 will index 3 to 5 of the slice is here to here so we get a slice representing this sub portion of the array logically it's the same effect as if we were to assign to S2 if we just directly did slice on the array itself but with indexes let's see S2 but not including 7 so both of these assignments here are assigning to S2 equivalent slice values very essential when dealing with slices is the append operator which given a slice returns a new slice with a new element tacked onto the end that's the gist of it here we have an array of 10 integers and then two slice of integer variables S and S2 and we assign to S a slice of the array from index 0 up to S8 and so we got a slice which has a length of 8 and a capacity of 10 because starting from index 0 through the end of the array there are 10 elements from that position and then if we use append on S well we're appending to a slice of integer so we need to specify an integer value so we're appending the value 1234 what we get back from this operation is a new slice which has a length 1 greater than the one we started with has a length of 8 now this new slice S2 has a length of 9 and the capacity though is still the same as 10 because this new slice value is pointing to index 0 of the array and through the end of the array from that position there are still 10 elements from there but because this slice has a length that's 1 greater we can now access index 8 of S2 and what we get back is the value we just appended 1234 be clear though that the original slice stored in variable S is unchanged it has a length of 8 it has a capacity of 10 and if we try and access index 8 of S then we get a runtime error it's because it's out of bounds given the length also be clear these two slices reference the same array so indexes 0 through 7 are shared between the two slices but then S2 has a length 1 greater so it also has an index 8 which S does not so if I set index 0 of the array to have the value negative 111 and then I get index 0 of S and S2 well those indexes of the slices correspond to index 0 of the array so they also now have the value negative 111 now in this scenario when we appended to S S is a slice pointing to an array where the length is less than the capacity and we're appending just one value so there was sufficient space in the underlying array for this value we want to do append there's a place to store this value 1234 in the array sometimes though when we use append maybe there's not sufficient space left in the array maybe there's not sufficient capacity and what happens there is something different in a slightly modified scenario if our original slice S represents the full range of the array it goes from index 0 up to but not including index 10 then the length of capacity will both be 10 and you might think then well if I'm going to append to the end of the length well that's beyond the end of the capacity so I can't append more values but actually no we can still append to this slice and so we do so here appending 1234 we get back a new slice stored in S2 and this new slice length is going to be 11 and the capacity is also going to be 11 or actually it might possibly be something greater we don't know it's guaranteed to be at least be 11 though it's going to be sufficient to store the length of the new slice and we'll explain why it might vary in a minute but anyway so we get the last index from this new slice index 10 and we get back the value which we just appended 1234 meanwhile the original slice is unchanged so the length and capacity of S are still 10 and if we try and get the value index 10 of this slice well that's still out of bounds for this slice if however we now assign to index 0 of the array the value negative 111 well the original slice S is still referencing that array so modifying the index 0 of the array is the same as modifying index 0 of S so if we get index 0 of S then we're getting back the value negative 1111 however if we get index 0 of S2 we get back 0 so what's happened here well what happened is that our original array starts out with the values 0 and all 10 slots and then we appended S a new value but there wasn't sufficient storage to store this new value in the underlying array and so what the append operator does is that actually creates a new array with sufficient size something that's going to be at least in this case 11 or maybe something greater for capacity but at least 11 it copies the existing 10 values it goes to the current length of the original array copies all those values over to the new array and then it sets this value in the last index of the new array so the new slice value stored in S2 points to a new array but for indexes 0 through 9 it's going to look the same as the original underlying array but now we're dealing with two separate arrays and so what happened down here is when we modify the original array which S points to S2 is no longer pointing to that so the change in the array doesn't affect S2 S and S2 are actually now referencing separate arrays the reason that the capacity might be something greater is that the common thing to do with slices is to in a loop repeatedly append items to the end of an existing slice and so the append operation when it has to create a new array and copy all those values well that's an expensive thing to do creating new arrays is expensive and copying all those values will be expensive especially the larger your slices arrays are the more expensive that's going to be and so as an optimization the language will try to somewhat intelligently be a little optimistic and assume that if you're going to do an append operation that requires creating an array it'll create an array that's somewhat bigger typically what it'll do it'll actually double the size so like most likely the size here was actually 20 not 11 it's something larger than actually needed in anticipation that you're likely going to do further appends and so if we can append to a slice where there's sufficient space left in the underlying array that's cheaper than when there isn't because again that requires a full creation of a new array and copying of the existing values now here's another example and this time I'm using the convenience of not creating an actual array variable we'll just directly create a slice but be clear again this is actually implicitly creating an array of five elements and the slice stored in S references that array of five integers and it's going to have a length and capacity of five if we then append to the slice multiple values which we can do we don't have to just append one thing at a time we can have any number of operands here so this is appending three elements to the end to the logical end of the slice S what we get back is a new slice stored in S2 which is going to have a length of eight because we're appending three things to a slice that had a length of five now we get back a length of eight and the new capacity is going to be well it might be eight but it could be something greater quite likely and so now if I access index six of S2 actually let's make that five I missed out that should be five if I access index five of S2 then I'm getting back the value one two three which is the first value appended onto the end of the original slice but now because this appended operation there wasn't sufficient capacity and the underlying array pointed to by S it created a new array with at least space for eight integers copied over the existing five integers 10, 20, 30, 40, 50 and then it set an index as five six and seven it set the values one two three four five six and seven eight nine and so if through the original slice we set index zero to have the value negative one one one that doesn't affect S2 so when we get into value zero at S2 we're actually getting back 10 because S2 is a slice that got a copy of the underlying array which back here had these values where the first value was 10 so that's why this operation down here is returning the value 10. Another operator we might use in connection with slices is the make operator which allows us to create a slice with a new underlying array where the size of that array is actually determined at runtime normally we can't do that when we create a raise it's fixed in size but with make we can specify the size with an integer expression that can be evaluated at runtime. So here where we use make we specify what kind of thing we're making in this case we're making a slice of integers and we specify the size and this value here in this case it's just a number plugged in directly but this could just be any expression that returns an integer so it could be like a function call that returns an integer in effect then the size doesn't have to be fixed at compile time but in this case what happens is that this returns a new slice with a new underlying array and the length and capacity of the slice is 6 so when we access indexes of the slice both 0 and 5 are in bounds 5 is the last index. Here's another example of using make and in this case we're specifying two numbers and when we do so the first is the length and the second is the capacity. If you just specify one number the length and capacity are going to be the same but sometimes you want them to be different sometimes you want the capacity to be bigger and as you would expect the length has to be smaller than the capacity otherwise you've got a runtime error in this case it's fine and here we have another operator copy which lets us copy elements of one slice to another slice and both slices have to be the same slice and here they're both slices of integers so that's okay but slices of the same type can have different lengths and so the question is what happens as in the case here where bar has a length of 3 and foo has a length of 5 well what happens is that the number of elements copied is the length of the smaller slice the shorter slice so what happens here is that the first 3 elements of foo 10, 20 and 30 are copied into the first 3 elements of bar so if we get the value at index 0 of bar we get 10, index 1 is 20 and index 2 is 30 earlier we looked at this example function sum which took in an array of 10 integers and returned the sum of all the values in the array and as we mentioned the great limitation of this function is that it only works on arrays of 10 integers not arrays of 9 integers or 11 or any other size because that's just a property of arrays is that the size is integral to the type so arrays of 10 are a distinct type compared to arrays of some other size for that reason we generally use slices as inputs to functions and outputs of functions instead of arrays because it's more flexible that way because they're not fixed in size here we have a version of sum that takes in a slice of integers and so this function can return the sum of any number of integers fixed particular number of integers for another example of where we would probably use a slice rather than an array say we have a function natural nums which given some value n some integer value n it returns an ordered collection of the natural numbers up to length n so say if the input is 5 then we get back the numbers 0, 1, 2, 3, 4 the first 5 natural numbers again we couldn't use an array for such a function because arrays are fixed in size so instead we have the function return a slice of integers and so here if I have my locals variable nums which is a slice of integers be clear the default value for a slice, the slice variable here it starts out as a slice value that references no array but it has a length of 0 and a capacity of 0 so there is a slice value it's just a slice value that represents no elements of any array and now if I use for ink to count from 0 up to but not including n each time through I'm going to append the value of i to nums and be clear what's happening here each time through the append operation gets us a new slice which has a length of 1 greater and we're trying to accumulate the result into one slice nums so each time through we're actually appending to nums and assigning the result to nums from nums we're creating a new slice and then clobbering what nums was it's very important to get this detail right because if we just do an append operation that's not actually modifying nums itself that's just creating a new slice value which would get lost if we didn't assign it to anything, if we didn't keep it so that we have to accumulate it in nums in any case once we get through this loop we then have nums which is a slice of natural numbers of length n and that's what we return now this code is perfectly correct but there's something objectively objectionable about it and that's how it uses the append the problem with append is that it can be potentially expensive especially if we're doing it each time through this loop if we can get away with it that's something we want to avoid we don't want to have to repeatedly append onto the same slice through many iterations because append in cases where there's no more capacity it has to create a new larger array and copy everything over that's very expensive when we use append in a loop like this then potentially we're having to do that expensive operation many times ideally instead of using append if we know how large the slice needs to be in the end we would just create a slice of sufficient size at the outset and then just use set as we go through the loop to populate the slice with values so instead of writing the code this way we can instead here again we have a slice of integers nums but then before the loop we just use make to specify its size which we know is going to be n and so we were just creating an array once of size n and then when we go through the loop we just use set to set at position i the value i so this code has the same result but it avoids using append in a loop which potentially can be very expensive now be clear instead of using slices we can always accomplish the same work by just using lists instead and arguably that would be more straightforward and simpler what that would look like in this case is you could write natural nums this way where we create an initial empty list before the loop and then we go through the loop and we push the value of i onto the list nums and then once we're through the loop we return nums but for similar reasons when we're creating in a loop for the same reason we generally want to avoid append it's a relatively expensive thing to do ideally the list would be its full size at the outset and we could just use set when we go through the loop and so we can actually use make on lists and specify a list of integers here of size n thereby creating a list with n integers thereby assigning to nums here a list of n integers of 0 but then we go through the loop and we set the value at index i to the value of i thereby populating the list with the natural numbers in order