Se um foreach for encontrado pelo compilador
foreach (element; range)
{
    // corpo do Loop...
}
ele é reescrito internamente de forma semelhante à seguinte:
for (auto __rangeCopy = range;
     !__rangeCopy.empty;
     __rangeCopy.popFront())
 {
    auto element = __rangeCopy.front;
    // Loop body...
}
Qualquer objeto que atenda à seguinte interface é chamado de range
(ou, mais especificamente, InputRange) e, portanto, é um tipo que pode ser iterado:
    interface InputRange(E)
    {
        bool empty();
        E front();
        void popFront();
    }
Dê uma olhada no exemplo à direita para inspecionar a implementação e o uso de um intervalo de entrada mais próximo.
Os ranges são lazy. Eles não serão avaliados até que sejam solicitados. Portanto, um range de um range infinito pode ser obtido:
42.repeat.take(3).writeln; // [42, 42, 42]
Se o objeto range for um tipo de valor, então o range será copiado e somente a cópia será utilizada:
auto r = 5.iota;
r.drop(5).writeln; // []
r.writeln; // [0, 1, 2, 3, 4]
Se o objeto range for um tipo de referência (por exemplo, class ou std.range.refRange),
então o range será utilizado e não será redefinido:
auto r = 5.iota;
auto r2 = refRange(&r);
r2.drop(5).writeln; // []
r2.writeln; // []
InputRanges copiáveis são ForwardRangesA maioria dos ranges na biblioteca padrão são structs e, portanto, a iteração foreach
geralmente não é destrutiva, embora não seja garantida. Se essa
garantia for importante, uma especialização de um InputRange pode ser usada
forward ranges com um método .save:
interface ForwardRange(E) : InputRange!E
{
    typeof(this) save();
}
// by value (Structs)
auto r = 5.iota;
auto r2 = refRange(&r);
r2.save.drop(5).writeln; // []
r2.writeln; // [0, 1, 2, 3, 4]
ForwardRanges pode ser estendido por ranges bidirecionais + ranges de acesso aleatórioHá duas extensões do ForwardRange copiável: (1) um range bidirecional
e (2) um range de acesso aleatório.
Um range bidirecional permite a iteração de trás para frente:
interface BidirectionalRange(E) : ForwardRange!E
{
     E back();
     void popBack();
}
5.iota.retro.writeln; // [4, 3, 2, 1, 0]
Um range de acesso aleatório tem um length (comprimento) conhecido e cada elemento pode ser acessado diretamente.
interface RandomAccessRange(E) : ForwardRange!E
{
     E opIndex(size_t i);
     size_t length();
}
Em termos práticos, o range de acesso aleatório mais conhecido é o array de D:
auto r = [4, 5, 6];
r[1].writeln; // 5
As funções em std.range e
std.algorithm fornecem
blocos de construção que fazem uso dessa interface. Os ranges permitem a
composição de algoritmos complexos por trás de um objeto que
pode ser iterado com facilidade. Além disso, os ranges permitem a criação de objetos lazy
que só executam um cálculo quando ele é realmente necessário
em uma iteração, por exemplo, quando o próximo elemento do range é acessado.
Algoritmos especiais do range serão apresentados mais adiante na seção
D's Gems.