Slices são objetos do tipo T[]
para qualquer tipo T
.
Os slices fornecem uma visualização em um subconjunto de uma matriz
de valores T
ou simplesmente apontam para todo o array.
Slices e arrays dinâmicos são a mesma coisa.
Um slice consiste em dois membros - um ponteiro para o elemento inicial e o tamanho do slice:
T* ptr;
size_t length; // unsigned 32 bit em 32bit, unsigned 64 bit em 64bit
Se um novo array dinâmico for criado, será retornado um slice para essa memória recém-alocada:
auto arr = new int[5];
assert(arr.length == 5); // referenciando memória em arr.ptr
A memória real alocada, nesse caso, é totalmente gerenciada pelo coletor de lixo. O slice retornado funciona como um ponto entre dois elementos subjacentes.
Usando um operador de slicing, também é possível obter um slice apontando para alguma memória já existente.
O operador de slicing pode ser aplicado a outro slice, arrays estáticos, structs/classes que implementam opSlice
e algumas outras entidades.
Em uma expressão de exemplo origin[start .. end]
, o operador de divisão é usado para obter
um slice de todos os elementos de origin
de start
até o elemento before end
:
auto newArr = arr[1 .. 4]; // O índice 4 NÃO está incluso
assert(newArr.length == 3);
newArr[0] = 10; // altera newArr[0] também conhecido como arr[1]
Esses slices geram um novo ponto de extremidade numa memória existente. Eles não criam uma nova cópia. Se nenhum slice tiver mais uma referência a essa memória - ou uma parte cortada dela - ela será liberada pelo coletor de lixo.
Usando slices, é possível escrever um código muito eficiente para coisas (como parsers, por exemplo) que operam somente em um bloco de memória e separam somente as partes que realmente precisam trabalhar. Dessa forma, não há necessidade de alocar novos blocos de memória.
Como visto na seção anterior, a expressão [$]
é uma forma abreviação de
arr.length
. Portanto, arr[$]
indexa o elemento após o final do slice e, portanto, geraria um RangeError
(se a verificação de limites não tiver sido desativada).