github.com/haraldrudell/parl@v0.4.176/iters/slice.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  // Package iters provides thread-safe iterators usable with Go’s for statement
     7  package iters
     8  
     9  import (
    10  	"sync/atomic"
    11  
    12  	"github.com/haraldrudell/parl/internal/cyclebreaker"
    13  )
    14  
    15  // Slice traverses a slice container. thread-safe
    16  type Slice[T any] struct {
    17  	slice []T // the slice providing values
    18  	// index is next slice-index to return
    19  	//	- if index == len(slice) there are no more values
    20  	index atomic.Uint64
    21  }
    22  
    23  // NewSliceIterator returns an iterator iterating over slice T values
    24  //   - thread-safe
    25  //   - uses non-pointer atomics
    26  func NewSliceIterator[T any](slice []T) (iterator Iterator[T]) { return &Slice[T]{slice: slice} }
    27  
    28  // Init implements the right-hand side of a short variable declaration in
    29  // the init statement for a Go “for” clause
    30  //
    31  // Usage:
    32  //
    33  //		for i, iterator := NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    34  //	   // i is pointer to slice element
    35  func (i *Slice[T]) Init() (iterationVariable T, iterator Iterator[T]) {
    36  	iterator = i
    37  	return
    38  }
    39  
    40  // Cond implements the condition statement of a Go “for” clause
    41  //   - the iterationVariable is updated by being provided as a pointer.
    42  //     iterationVariable cannot be nil
    43  //   - errp is an optional error pointer receiving any errors during iterator execution
    44  //   - condition is true if iterationVariable was assigned a value and the iteration should continue
    45  //
    46  // Usage:
    47  //
    48  //		for i, iterator := NewSlicePointerIterator(someSlice).Init(); iterator.Cond(&i); {
    49  //	   // i is pointer to slice element
    50  func (i *Slice[T]) Cond(iterationVariablep *T, errp ...*error) (condition bool) {
    51  	if iterationVariablep == nil {
    52  		cyclebreaker.NilError("iterationVariablep")
    53  	}
    54  
    55  	// check for next value
    56  	var value T
    57  	if value, condition = i.Next(); condition {
    58  		*iterationVariablep = value
    59  	}
    60  
    61  	return // condition and iterationVariablep updated, errp unchanged
    62  }
    63  
    64  // Next advances to next item and returns it
    65  //   - if hasValue true, value contains the next value
    66  //   - otherwise, no more items exist and value is the data type zero-value
    67  func (i *Slice[T]) Next() (value T, hasValue bool) {
    68  	for {
    69  		var index = i.index.Load()
    70  		if int(index) == len(i.slice) {
    71  			return // no more values
    72  		}
    73  		if hasValue = i.index.CompareAndSwap(index, index+1); hasValue {
    74  			value = i.slice[index]
    75  			return
    76  		}
    77  	}
    78  }
    79  
    80  // Cancel release resources for this iterator. Thread-safe
    81  //   - not every iterator requires a Cancel invocation
    82  func (i *Slice[T]) Cancel(errp ...*error) (err error) {
    83  	for {
    84  		var index = i.index.Load()
    85  		if int(index) == len(i.slice) {
    86  			return // no more values: already canceled
    87  		}
    88  		if i.index.CompareAndSwap(index, uint64(len(i.slice))) {
    89  			return // cancel complete
    90  		}
    91  	}
    92  }