> For the complete documentation index, see [llms.txt](https://swift.unicorndonkeys.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://swift.unicorndonkeys.com/guida-al-linguaggio/tipi-opachi-e-tipi-protocollo-boxed.md).

# Tipi opachi e tipi protocollo boxed (Opaque and Boxed Protocol Types)

Nascondi i dettagli di implementazione relativi al tipo di un valore.

Swift fornisce due modi per nascondere i dettagli relativi al tipo di un valore: i tipi opachi e i tipi protocollo boxed. Nascondere le informazioni di tipo è utile ai confini tra un modulo e il codice che chiama il modulo, perché il tipo sottostante del valore di ritorno può rimanere privato.

Una funzione o un metodo che restituisce un tipo opaco nasconde le informazioni di tipo del suo valore di ritorno. Invece di fornire un tipo concreto come tipo di ritorno della funzione, il valore di ritorno viene descritto in termini dei protocolli che supporta. I tipi opachi preservano l'identità del tipo — il compilatore ha accesso alle informazioni di tipo, ma i client del modulo no.

Un tipo protocollo boxed può memorizzare un'istanza di qualsiasi tipo conforme al protocollo dato. I tipi protocollo boxed non preservano l'identità del tipo — il tipo specifico del valore non è noto fino al momento dell'esecuzione, e può cambiare nel tempo man mano che vengono memorizzati valori diversi.

## Il problema che i tipi opachi risolvono <a href="#il-problema-che-i-tipi-opachi-risolvono" id="il-problema-che-i-tipi-opachi-risolvono"></a>

Per esempio, supponi di scrivere un modulo che disegna forme in ASCII art. La caratteristica fondamentale di una forma in ASCII art è una funzione `draw()` che restituisce la rappresentazione in forma di stringa di quella forma, che puoi usare come requisito del protocollo `Shape`:

```swift
protocol Shape {
    func draw() -> String
}

struct Triangle: Shape {
    var size: Int
    func draw() -> String {
       var result: [String] = []
       for length in 1...size {
           result.append(String(repeating: "*", count: length))
       }
       return result.joined(separator: "\n")
    }
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***
```

Potresti usare i generici per implementare operazioni come il capovolgimento verticale di una forma, come mostrato nel codice seguente. Tuttavia, questo approccio ha un'importante limitazione: il risultato capovolto espone gli esatti tipi generici che sono stati usati per crearlo.

```swift
struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *
```

Questo approccio alla definizione di una struttura `JoinedShape<T: Shape, U: Shape>` che unisce verticalmente due forme, come mostra il codice seguente, produce tipi come `JoinedShape<Triangle, FlippedShape<Triangle>>` dall'unione di un triangolo con un triangolo capovolto.

```swift
struct JoinedShape<T: Shape, U: Shape>: Shape {
    var top: T
    var bottom: U
    func draw() -> String {
       return top.draw() + "\n" + bottom.draw()
    }
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *
```

Esporre informazioni dettagliate sulla creazione di una forma fa sì che tipi che non sono destinati a far parte dell'interfaccia pubblica del modulo ASCII art trapelino all'esterno a causa della necessità di dichiarare il tipo di ritorno completo. Il codice all'interno del modulo potrebbe costruire la stessa forma in molti modi diversi, e l'altro codice all'esterno del modulo che usa la forma non dovrebbe dover tenere conto dei dettagli di implementazione relativi all'elenco delle trasformazioni. I tipi wrapper come `JoinedShape` e `FlippedShape` non interessano agli utenti del modulo, e non dovrebbero essere visibili. L'interfaccia pubblica del modulo consiste in operazioni come l'unione e il capovolgimento di una forma, e queste operazioni restituiscono un altro valore `Shape`.

## Restituire un tipo opaco <a href="#restituire-un-tipo-opaco" id="restituire-un-tipo-opaco"></a>

Puoi pensare a un tipo opaco come all'inverso di un tipo generico. I tipi generici permettono al codice che chiama una funzione di scegliere il tipo per i parametri e il valore di ritorno di quella funzione in un modo che è astratto rispetto all'implementazione della funzione. Per esempio, la funzione nel codice seguente restituisce un tipo che dipende da chi la chiama:

```swift
func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }
```

Il codice che chiama `max(_:_:)` sceglie i valori di `x` e `y`, e il tipo di quei valori determina il tipo concreto di `T`. Il codice chiamante può usare qualsiasi tipo conforme al protocollo `Comparable`. Il codice all'interno della funzione è scritto in modo generale così da poter gestire qualunque tipo fornisca il chiamante. L'implementazione di `max(_:_:)` usa solo funzionalità condivise da tutti i tipi `Comparable`.

Questi ruoli sono invertiti per una funzione con un tipo di ritorno opaco. Un tipo opaco permette all'implementazione della funzione di scegliere il tipo del valore che restituisce in un modo che è astratto rispetto al codice che chiama la funzione. Per esempio, la funzione dell'esempio seguente restituisce un trapezio senza esporre il tipo sottostante di quella forma.

```swift
struct Square: Shape {
    var size: Int
    func draw() -> String {
        let line = String(repeating: "*", count: size)
        let result = Array<String>(repeating: line, count: size)
        return result.joined(separator: "\n")
    }
}

func makeTrapezoid() -> some Shape {
    let top = Triangle(size: 2)
    let middle = Square(size: 2)
    let bottom = FlippedShape(shape: top)
    let trapezoid = JoinedShape(
        top: top,
        bottom: JoinedShape(top: middle, bottom: bottom)
    )
    return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
// *
// **
// **
// **
// **
// *
```

La funzione `makeTrapezoid()` di questo esempio dichiara il suo tipo di ritorno come `some Shape`; di conseguenza, la funzione restituisce un valore di un qualche tipo dato conforme al protocollo `Shape`, senza specificare alcun tipo concreto particolare. Scrivere `makeTrapezoid()` in questo modo le permette di esprimere l'aspetto fondamentale della sua interfaccia pubblica — il valore che restituisce è una forma — senza rendere i tipi specifici di cui la forma è composta parte della sua interfaccia pubblica. Questa implementazione usa due triangoli e un quadrato, ma la funzione potrebbe essere riscritta per disegnare un trapezio in molti altri modi senza cambiare il suo tipo di ritorno.

Questo esempio mette in evidenza il modo in cui un tipo di ritorno opaco è come l'inverso di un tipo generico. Il codice all'interno di `makeTrapezoid()` può restituire qualsiasi tipo di cui abbia bisogno, purché quel tipo sia conforme al protocollo `Shape`, come fa il codice chiamante per una funzione generica. Il codice che chiama la funzione deve essere scritto in modo generale, come l'implementazione di una funzione generica, così da poter lavorare con qualsiasi valore `Shape` restituito da `makeTrapezoid()`.

Puoi anche combinare i tipi di ritorno opachi con i generici. Le funzioni nel codice seguente restituiscono entrambe un valore di un qualche tipo conforme al protocollo `Shape`.

```swift
func flip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape: shape)
}
func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
    JoinedShape(top: top, bottom: bottom)
}

let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *
```

Il valore di `opaqueJoinedTriangles` in questo esempio è lo stesso di `joinedTriangles` nell'esempio con i generici nella sezione [Il problema che i tipi opachi risolvono](/guida-al-linguaggio/tipi-opachi-e-tipi-protocollo-boxed.md#il-problema-che-i-tipi-opachi-risolvono) più indietro in questo capitolo. Tuttavia, a differenza del valore di quell'esempio, `flip(_:)` e `join(_:_:)` racchiudono i tipi sottostanti restituiti dalle operazioni generiche sulle forme in un tipo di ritorno opaco, il che impedisce che quei tipi siano visibili. Entrambe le funzioni sono generiche perché i tipi su cui si basano sono generici, e i parametri di tipo della funzione trasmettono le informazioni di tipo necessarie a `FlippedShape` e `JoinedShape`.

Se una funzione con un tipo di ritorno opaco restituisce un valore da più punti, tutti i possibili valori di ritorno devono avere lo stesso tipo. Per una funzione generica, quel tipo di ritorno può usare i parametri di tipo generici della funzione, ma deve comunque essere un unico tipo. Per esempio, ecco una versione *non valida* della funzione di capovolgimento delle forme che include un caso speciale per i quadrati:

```swift
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
    if shape is Square {
        return shape // Errore: i tipi di ritorno non corrispondono.
    }
    return FlippedShape(shape: shape) // Errore: i tipi di ritorno non corrispondono.
}
```

Se chiami questa funzione con uno `Square`, restituisce uno `Square`; altrimenti, restituisce una `FlippedShape`. Questo viola il requisito di restituire valori di un solo tipo e rende `invalidFlip(_:)` codice non valido. Un modo per correggere `invalidFlip(_:)` è spostare il caso speciale per i quadrati nell'implementazione di `FlippedShape`, il che permette a questa funzione di restituire sempre un valore `FlippedShape`:

```swift
struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        if shape is Square {
           return shape.draw()
        }
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}
```

Il requisito di restituire sempre un unico tipo non ti impedisce di usare i generici in un tipo di ritorno opaco. Ecco un esempio di funzione che incorpora il suo parametro di tipo nel tipo sottostante del valore che restituisce:

```swift
func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
    return Array<T>(repeating: shape, count: count)
}
```

In questo caso, il tipo sottostante del valore di ritorno varia a seconda di `T`: qualunque forma venga passata, `repeat(shape:count:)` crea e restituisce un array di quella forma. Ciononostante, il valore di ritorno ha sempre lo stesso tipo sottostante `[T]`, quindi rispetta il requisito secondo cui le funzioni con tipi di ritorno opachi devono restituire valori di un solo tipo.

## Tipi protocollo boxed <a href="#tipi-protocollo-boxed" id="tipi-protocollo-boxed"></a>

Un tipo protocollo boxed è talvolta chiamato anche *tipo esistenziale*, denominazione che deriva dall'espressione "esiste un tipo *T* tale che *T* è conforme al protocollo". Per creare un tipo protocollo boxed, scrivi `any` prima del nome di un protocollo. Ecco un esempio:

```swift
struct VerticalShapes: Shape {
    var shapes: [any Shape]
    func draw() -> String {
        return shapes.map { $0.draw() }.joined(separator: "\n\n")
    }
}

let largeTriangle = Triangle(size: 5)
let largeSquare = Square(size: 5)
let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
print(vertical.draw())
```

Nell'esempio qui sopra, `VerticalShapes` dichiara il tipo di `shapes` come `[any Shape]` — un array di elementi `Shape` boxed. Ogni elemento dell'array può essere di un tipo diverso, e ciascuno di quei tipi deve essere conforme al protocollo `Shape`. Per supportare questa flessibilità in fase di esecuzione, Swift aggiunge un livello di indirezione quando necessario — questa indirezione è chiamata *box* (scatola), e ha un costo in termini di prestazioni.

All'interno del tipo `VerticalShapes`, il codice può usare metodi, proprietà e subscript richiesti dal protocollo `Shape`. Per esempio, il metodo `draw()` di `VerticalShapes` chiama il metodo `draw()` su ciascun elemento dell'array. Questo metodo è disponibile perché `Shape` richiede un metodo `draw()`. Al contrario, tentare di accedere alla proprietà `size` del triangolo, o a qualsiasi altra proprietà o metodo non richiesto da `Shape`, produce un errore.

Metti a confronto i tre tipi che potresti usare per `shapes`:

* Usare i generici, scrivendo `struct VerticalShapes<S: Shape>` e `var shapes: [S]`, crea un array i cui elementi sono di un qualche tipo di forma specifico, e in cui l'identità di quel tipo specifico è visibile a qualsiasi codice che interagisce con l'array.
* Usare un tipo opaco, scrivendo `var shapes: [some Shape]`, crea un array i cui elementi sono di un qualche tipo di forma specifico, e in cui l'identità di quel tipo specifico è nascosta.
* Usare un tipo protocollo boxed, scrivendo `var shapes: [any Shape]`, crea un array che può memorizzare elementi di tipi diversi, e in cui le identità di quei tipi sono nascoste.

In questo caso, un tipo protocollo boxed è l'unico approccio che permette a chi chiama `VerticalShapes` di mescolare insieme diversi tipi di forme.

Puoi usare una conversione `as` quando conosci il tipo sottostante di un valore boxed. Per esempio:

```swift
if let downcastTriangle = vertical.shapes[0] as? Triangle {
    print(downcastTriangle.size)
}
// Stampa "5".
```

Per maggiori informazioni, vedi [Downcasting](/guida-al-linguaggio/type-casting.md#downcasting).

## Differenze tra tipi opachi e tipi protocollo boxed <a href="#differenze-tra-tipi-opachi-e-tipi-protocollo-boxed" id="differenze-tra-tipi-opachi-e-tipi-protocollo-boxed"></a>

Restituire un tipo opaco sembra molto simile a usare un tipo protocollo boxed come tipo di ritorno di una funzione, ma questi due generi di tipo di ritorno differiscono nel fatto di preservare o meno l'identità del tipo. Un tipo opaco si riferisce a un tipo specifico, anche se chi chiama la funzione non è in grado di vedere quale; un tipo protocollo boxed può riferirsi a qualsiasi tipo conforme al protocollo. In generale, i tipi protocollo boxed ti danno maggiore flessibilità riguardo ai tipi sottostanti dei valori che memorizzano, e i tipi opachi ti permettono di dare garanzie più forti su quei tipi sottostanti.

Per esempio, ecco una versione di `flip(_:)` che usa un tipo protocollo boxed come tipo di ritorno invece di un tipo di ritorno opaco:

```swift
func protoFlip<T: Shape>(_ shape: T) -> Shape {
    return FlippedShape(shape: shape)
}
```

Questa versione di `protoFlip(_:)` ha lo stesso corpo di `flip(_:)`, e restituisce sempre un valore dello stesso tipo. A differenza di `flip(_:)`, il valore restituito da `protoFlip(_:)` non è obbligato ad avere sempre lo stesso tipo — deve soltanto essere conforme al protocollo `Shape`. In altre parole, `protoFlip(_:)` stabilisce con il suo chiamante un contratto di API molto più lasco di quello stabilito da `flip(_:)`. Si riserva la flessibilità di restituire valori di più tipi:

```swift
func protoFlip<T: Shape>(_ shape: T) -> Shape {
    if shape is Square {
        return shape
    }

    return FlippedShape(shape: shape)
}
```

La versione rivista del codice restituisce un'istanza di `Square` o un'istanza di `FlippedShape`, a seconda della forma che viene passata. Due forme capovolte restituite da questa funzione potrebbero avere tipi completamente diversi. Altre versioni valide di questa funzione potrebbero restituire valori di tipi diversi quando capovolgono più istanze della stessa forma. Le informazioni meno specifiche sul tipo di ritorno di `protoFlip(_:)` significano che molte operazioni che dipendono dalle informazioni di tipo non sono disponibili sul valore restituito. Per esempio, non è possibile scrivere un operatore `==` che confronti i risultati restituiti da questa funzione.

```swift
let protoFlippedTriangle = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
protoFlippedTriangle == sameThing  // Errore
```

L'errore sull'ultima riga dell'esempio si verifica per diverse ragioni. Il problema immediato è che `Shape` non include un operatore `==` tra i requisiti del suo protocollo. Se provi ad aggiungerne uno, il problema successivo che incontrerai è che l'operatore `==` deve conoscere i tipi dei suoi argomenti sinistro e destro. Questo genere di operatore di solito accetta argomenti di tipo `Self`, corrispondente a qualunque tipo concreto adotti il protocollo, ma aggiungere un requisito `Self` al protocollo non consente la cancellazione del tipo (type erasure) che avviene quando usi il protocollo come tipo.

Usare un tipo protocollo boxed come tipo di ritorno di una funzione ti dà la flessibilità di restituire qualsiasi tipo conforme al protocollo. Tuttavia, il costo di quella flessibilità è che alcune operazioni non sono possibili sui valori restituiti. L'esempio mostra come l'operatore `==` non sia disponibile — dipende da informazioni di tipo specifiche che non vengono preservate usando un tipo protocollo boxed.

Un altro problema di questo approccio è che le trasformazioni delle forme non si annidano. Il risultato del capovolgimento di un triangolo è un valore di tipo `Shape`, e la funzione `protoFlip(_:)` accetta un argomento di un qualche tipo conforme al protocollo `Shape`. Tuttavia, un valore di un tipo protocollo boxed non è conforme a quel protocollo; il valore restituito da `protoFlip(_:)` non è conforme a `Shape`. Ciò significa che codice come `protoFlip(protoFlip(smallTriangle))`, che applica più trasformazioni, non è valido perché la forma capovolta non è un argomento valido per `protoFlip(_:)`.

Al contrario, i tipi opachi preservano l'identità del tipo sottostante. Swift può inferire i tipi associati, il che ti permette di usare un valore di ritorno opaco in punti in cui un tipo protocollo boxed non può essere usato come valore di ritorno. Per esempio, ecco una versione del protocollo `Container` di [Generici](/guida-al-linguaggio/generici.md):

```swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
extension Array: Container { }
```

Non puoi usare `Container` come tipo di ritorno di una funzione perché quel protocollo ha un tipo associato. Non puoi nemmeno usarlo come vincolo in un tipo di ritorno generico perché al di fuori del corpo della funzione non ci sono informazioni sufficienti per inferire quale debba essere il tipo generico.

```swift
// Errore: un protocollo con tipi associati non può essere usato come tipo di ritorno.
func makeProtocolContainer<T>(item: T) -> Container {
    return [item]
}

// Errore: informazioni insufficienti per inferire C.
func makeProtocolContainer<T, C: Container>(item: T) -> C {
    return [item]
}
```

Usare il tipo opaco `some Container` come tipo di ritorno esprime il contratto di API desiderato — la funzione restituisce un contenitore, ma si astiene dallo specificare il tipo del contenitore:

```swift
func makeOpaqueContainer<T>(item: T) -> some Container {
    return [item]
}
let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]
print(type(of: twelve))
// Stampa "Int".
```

Il tipo di `twelve` viene inferito come `Int`, il che illustra il fatto che l'inferenza di tipo funziona con i tipi opachi. Nell'implementazione di `makeOpaqueContainer(item:)`, il tipo sottostante del contenitore opaco è `[T]`. In questo caso, `T` è `Int`, quindi il valore di ritorno è un array di interi e il tipo associato `Item` viene inferito come `Int`. Il subscript di `Container` restituisce `Item`, il che significa che anche il tipo di `twelve` viene inferito come `Int`.

## Tipi parametro opachi <a href="#tipi-parametro-opachi" id="tipi-parametro-opachi"></a>

Oltre a scrivere `some` per restituire un tipo opaco, puoi anche scrivere `some` nel tipo di un parametro di una funzione, di un subscript o di un inizializzatore. Tuttavia, quando scrivi `some` nel tipo di un parametro, si tratta semplicemente di una sintassi più breve per i generici, non di un tipo opaco. Per esempio, le due funzioni seguenti sono equivalenti:

```swift
func drawTwiceGeneric<SomeShape: Shape>(_ shape: SomeShape) -> String {
    let drawn = shape.draw()
    return drawn + "\n" + drawn
}

func drawTwiceSome(_ shape: some Shape) -> String {
    let drawn = shape.draw()
    return drawn + "\n" + drawn
}
```

La funzione `drawTwiceGeneric(_:)` dichiara un parametro di tipo generico chiamato `SomeShape`, con un vincolo che richiede che `SomeShape` sia conforme al protocollo `Shape`. La funzione `drawTwiceSome(_:)` usa il tipo `some Shape` per il suo argomento. Questo crea per la funzione un nuovo parametro di tipo generico senza nome con un vincolo che richiede che il tipo sia conforme al protocollo `Shape`. Poiché il tipo generico non ha un nome, non puoi fare riferimento a quel tipo altrove nella funzione.

Se scrivi `some` prima del tipo di più di un parametro, ciascuno dei tipi generici è indipendente. Per esempio:

```swift
func combine(shape s1: some Shape, with s2: some Shape) -> String {
    return s1.draw() + "\n" + s2.draw()
}

combine(smallTriangle, trapezoid)
```

Nella funzione `combine(shape:with:)`, i tipi del primo e del secondo parametro devono essere entrambi conformi al protocollo `Shape`, ma non c'è alcun vincolo che richieda che siano dello stesso tipo. Quando chiami `combine(shape:with)`, puoi passare due forme diverse — in questo caso, un triangolo e un trapezio.

A differenza della sintassi per i parametri di tipo generici con nome, descritta nel capitolo [Generici](/guida-al-linguaggio/generici.md), questa sintassi leggera non può includere una clausola `where` generica né alcun vincolo di uguaglianza di tipo (`==`). Inoltre, usare la sintassi leggera per vincoli molto complessi può rendere il codice difficile da leggere.
