AL+

Limba: RO (română) EN (English)

Scrieți-ne: paul@alplustech.com

Limitele unui interval Rust au alt rol decât progresul iteratorului

Separați limitele Rust de progresul iteratorului, folosiți RangeBounds pentru tipuri vechi și noi și distingeți tipurile stabile de sintaxa intervalelor.

Read this article in English

Un interval păstrat în modelul unui document descrie, de obicei, o regiune. Un iterator arată până unde a ajuns o parcurgere. Când aceeași valoare îndeplinește ambele roluri, efectul copierii devine greu de explicat. O descriere reutilizabilă ar trebui să își păstreze limitele; o parcurgere trebuie să păstreze faptul că unele elemente au fost deja consumate. Un API este mai ușor de înțeles dacă reprezintă separat cele două sensuri.

Schimbarea tipurilor pentru intervale din Rust oferă un exemplu util al acestei separări. Pentru autorul unei biblioteci, întrebarea practică este ce sens trebuie să transmită un parametru. O funcție care selectează o porțiune are nevoie de limite. O funcție care continuă prelucrarea are nevoie de progres. Dacă alegeți trăsătura sau tipul concret după ce lămuriți această întrebare, apelanții nu mai primesc o stare pe care operația nu intenționa să o folosească.

Regiunea și următorul element răspund la întrebări diferite

Să luăm un exemplu ipotetic: o adnotare care marchează o regiune dintr-un document. Două operații independente pot avea nevoie de aceeași adnotare. Una afișează textul selectat, iar cealaltă îi verifică conținutul. Niciuna nu ar trebui să modifice adnotarea doar fiindcă o citește. Copierea adnotării ar trebui, așadar, să copieze selecția, fără o legătură implicită cu volumul de lucru încheiat de vreuna dintre operații.

O parcurgere are alt contract. După ce o operație consumă câteva elemente, următoarea poate avea obligația să continue din acel punct. Copierea stării produce o altă parcurgere, aflată într-o anumită poziție. Acest lucru poate fi intenționat, dar are consecințe. Dacă duplicarea se produce implicit, când o metodă preia prin valoare obiectul asupra căruia este apelată, cititorul poate confunda progresul copiei cu progresul originalului.

Motivația din RFC 3550 descrie această interacțiune dintre Copy și Iterator. Principiul util pentru proiectare este să faceți vizibil începutul parcurgerii. O valoare care implementează IntoIterator poate descrie ce urmează să fie parcurs; conversia creează obiectul care urmărește consumarea elementelor. Descrierea care poate fi copiată și iteratorul rezultat au astfel responsabilități diferite.

Nu orice copie a unui iterator este greșită. Copierea implicită este însă o alegere nepotrivită când cititorii se așteaptă, în mod normal, la avansare. O duplicare explicită poate exprima în continuare o ramificare deliberată a prelucrării. API-ul ar trebui să permită deosebirea acestei decizii de reutilizarea obișnuită a descrierii unei regiuni.

Tipurile stabile și sintaxa au calendare separate

Anunțul Rust 1.96, din 28 mai 2026, aduce în versiunea stabilă noile tipuri din core::range, care separă aceste roluri. Ele pot implementa Copy dacă și limitele îl implementează. Un detaliu important: în acea versiune, expresii precum 0..1 produc în continuare tipurile vechi. Stabilizarea unui tip din bibliotecă nu schimbă, prin ea însăși, tipul produs de sintaxa cunoscută.

Distincția contează când evaluați exemple. O demonstrație construită în jurul sintaxei prevăzute de propunere nu stabilește comportamentul unui anumit compilator lansat. RFC-ul are data de început 18 decembrie 2023 și propunea o tranziție pentru ediția 2024. Data de început nu este nici acceptarea, nici livrarea funcționalității, iar calendarul propus nu s-a realizat așa cum fusese scris. Pentru delimitarea versiunii istorice, anunțul lansării stabilește ce se poate afirma.

Pentru o echipă care întreține o bibliotecă, migrarea este deci o decizie privind interfața, nu o simplă înlocuire de text. Un parametru poate accepta intenționat atât apelanții vechi, cât și pe cei noi. Un câmp păstrat în memorie poate necesita o reprezentare concretă. Aceste alegeri se pot face separat. Adoptarea unei reprezentări în interiorul bibliotecii nu înseamnă că toți apelanții o construiesc deja prin sintaxa intervalelor.

Acceptați informația de care are nevoie operația

Anunțul recomandă autorilor de biblioteci RangeBounds pentru interfețele care pot accepta ambele reprezentări. Alegerea se potrivește când operația are nevoie doar de începutul și sfârșitul unei regiuni. Ea arată că apelantul furnizează o descriere, fără să impună o anumită implementare a unui interval ce poate fi parcurs.

Trăsătura nu stabilește întregul contract al operației. API-ul trebuie să definească în continuare cum interpretează capetele incluse, excluse sau nelimitate. Trebuie stabilit și dacă limitele furnizate au sens pentru datele accesate. Acceptarea unei reprezentări mai generale a limitelor nu transformă o regiune invalidă într-una validă.

Pentru o operație care consumă efectiv elemente, IntoIterator sau Iterator exprimă altă cerință. Primul poate accepta o valoare din care începe parcurgerea. Al doilea poate accepta intenționat o parcurgere deja începută. Dacă tratați aceste situații separat, puteți numi clar un comportament care altfel rămâne ascuns în spatele unui tip de parametru comod.

O singură parcurgere poate rămâne simplă

Argumentul cel mai puternic pentru un interval cu stare este un algoritm obișnuit cu o singură parcurgere. Un iterator local, al cărui progres este folosit imediat, este comod. O descriere reutilizabilă separată poate aduce prea puțin. Codul care nu păstrează și nu partajează descrierea unui interval nu are nevoie de încă o abstracție doar pentru a respecta o preferință generală de proiectare.

Separarea devine utilă când o valoare trece printr-o interfață sau rămâne în uz după o parcurgere. Examinați mai întâi adnotările păstrate, selecțiile reutilizabile și parametrii transmiși mai multor operații. Stabiliți dacă sensul lor ar trebui să se schimbe după iterare. Dacă da, documentați punctul de continuare. Dacă nu, păstrați limitele separat de iteratorul care consumă elementele.

Într-un proiect de dezvoltare software la comandă, aceasta este o cerință concretă pentru interfață, care merită stabilită înaintea implementării: ce valori descriu lucrul de făcut și ce valori înregistrează lucrul deja încheiat. Păstrați regiunea reutilizabilă intactă și faceți explicită crearea fiecărei parcurgeri.