257

What is the Go way for extracting the last element of a slice?

var slice []int

slice = append(slice, 2)
slice = append(slice, 7)

slice[len(slice)-1:][0] // Retrieves the last element

The solution above works, but seems awkward.

Dave C
  • 7,729
  • 4
  • 49
  • 65
Morgan Wilde
  • 16,795
  • 10
  • 53
  • 99

4 Answers4

448

For just reading the last element of a slice:

sl[len(sl)-1]

For removing it:

sl = sl[:len(sl)-1]

See this page about slice tricks

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Toni Cárdenas
  • 6,510
  • 1
  • 18
  • 18
  • 59
    Thanks a bunch! Even though it does seem silly they didn't add the `-1` index Python has... – Morgan Wilde Mar 20 '14 at 15:00
  • 9
    I do like the `-1` from Python, although it often lead to hard-to-debug errors. – weberc2 Mar 20 '14 at 15:14
  • 18
    They left it outside consciously. It was non-obvious and prone to errors. Go overall is circumspect about 'too much meaning'; it also doesn't feature method/operator overloading, default values for function params, etc. which IMHO goes in a similar philosophical vein. See this discussion and others: https://groups.google.com/forum/#!topic/golang-nuts/yn9Q6HhgWi0 – Toni Cárdenas Mar 20 '14 at 15:27
  • 2
    I am not sure but I got `panic: runtime error: index out of range` for `profiles[len(profiles)-1].UserId`, I guess the length of the slice is 0 so it panics? – tom10271 Aug 16 '19 at 02:20
  • 2
    @tom10271 Yes, you can't get the last element of a slice if there's no such element, ie. if there are no elements at all. – Toni Cárdenas Aug 16 '19 at 11:20
  • 1
    Why isn't there a pop() function that both removes the last element and also returns it... – a3y3 Mar 03 '22 at 03:19
  • 1
    @a3y3 or `tail()` or `head()` etc etc. There are massive holes in the standard library _from the perspective of users,_ but consciously omitted to keep things sensible and "circumspect". Now that we have generics, perhaps this will change, but I highly doubt it. – Benjamin R Nov 10 '22 at 09:49
8

If you can use Go 1.18 or above and you often need to access the last element of a slice of some arbitrary element type, the use of a small custom function can improve readability at call sites. See also Roger Peppe's Generics Unconstrained talk, where he describes a similar function to get the first element (if any) of a slice.

package main

import "fmt"

func Last[E any](s []E) (E, bool) {
    if len(s) == 0 {
        var zero E
        return zero, false
    }
    return s[len(s)-1], true
}

func main() {
    var numbers []int
    fmt.Println(Last(numbers)) // 0 false
    numbers = []int{4, 8, 15, 16, 23, 42}
    fmt.Println(Last(numbers)) // 42 true
}

(Playground)

No need to create a library for that Last function, though; a little copying is better than a little dependency.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
  • 3
    and now there's [a proposal](https://github.com/golang/go/issues/53510) to add that to `x/exp/slices` – blackgreen Jul 08 '22 at 19:56
  • @blackgreen and promptly rejected :( – Benjamin R Nov 10 '22 at 09:53
  • 2
    @BenjaminR mixed feelings about that, as you can see from jub0bs answer the function is really really easy to implement. I think I can get behind [this comment from Rob Pike](https://github.com/golang/go/issues/53510#issuecomment-1183693014) – blackgreen Nov 10 '22 at 09:57
  • 1
    @blackgreen In the 8 years I have been using Go, I think I don't just speak for myself when I say that that rationale is precisely why people who hate Go *really* hate it. If I was a maintainer I would agree with Pike, as a USER, though, I can't. I'm happy to have "one and only one way of doing things", but using `s[len(s)-1]` isn't actually *doing* the thing, because you need to check `s != nil` and `len(s) > 0` before doing that. Every Single Time. And I value a lack of inane redundancy. – Benjamin R Nov 10 '22 at 10:04
  • 1
    @BenjaminR checking length is all you need to do. The zero value of a slice is nil, and will have a length and capacity of 0. https://go.dev/play/p/gkMjm5CJ26A – lachlan.p.jordan Dec 08 '22 at 21:01
  • Is there any particular advantage to using `var zero E` and then `return zero, false` instead of `return *new(E), false`? – Daniel Morell Dec 30 '22 at 17:38
  • @DanielMorell I can think of one advantage: readability. In my opinion, `var zero E` then returning `zero, false` is more readable. By comparison, `return *new(E), false` is cryptic. [Clear is better than clever](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=14m34s). – jub0bs Dec 30 '22 at 18:00
5

You can use the len(arr) function, although it will return the length of the slice starting from 1, and as Go arrays/slices start from index 0 the last element is effectively len(arr)-1

Example:

arr := []int{1,2,3,4,5,6} // 6 elements, last element at index 5
fmt.Println(len(arr)) // 6
fmt.Println(len(arr)-1) // 5
fmt.Println(arr[len(arr)-1]) // 6 <- element at index 5 (last element)
leonardo
  • 1,686
  • 15
  • 15
0

What is even more awkward is your program crashing on empty slices!

To contend with empty slices -- zero length causing panic: runtime error, you could have an if/then/else sequence, or you can use a temporary slice to solve the problem.

package main

import (
    "fmt"
)

func main() {
    // test when slice is not empty
    itemsTest1 := []string{"apple", "grape", "orange", "peach", "mango"}

    tmpitems := append([]string{"none"},itemsTest1...)
    lastitem := tmpitems[len(tmpitems)-1]
    fmt.Printf("lastitem: %v\n", lastitem)

    // test when slice is empty
    itemsTest2 := []string{}

    tmpitems = append([]string{"none"},itemsTest2...) // <--- put a "default" first
    lastitem = tmpitems[len(tmpitems)-1]
    fmt.Printf("lastitem: %v\n", lastitem)
}

which will give you this output:

lastitem: mango
lastitem: none

For []int slices you might want a -1 or 0 for the default value.

Thinking at a higher level, if your slice always carries a default value then the "tmp" slice can be eliminated.

warrens
  • 1,661
  • 18
  • 16