Correct `max` function implementation for variadic generics?

I have this partially written code which will work somewhat similarly to SwiftUI stacks. I need to figure out how to get the width of the widest drawable.

public struct DrawableVStack<each Inner: Drawable>: Drawable {
    public let inner: (repeat each Inner)
    public let width: Int
    public let height: Int
    
    public init(_ alignment: Alignment = .centered, spacing: Int = 0, @DrawableBuilder drawables: () -> (repeat each Inner)) {
        let inner = drawables()
        self.width = max(repeat (each inner).width) // ERROR: Type of expression is ambiguous without a type annotation
    }
    
    public subscript(x: Int, y: Int) -> Color {
        fatalError()
    }
    
    public enum Alignment { case leading, trailing, centered } // ERROR: Enums cannot declare a type pack
}

The first issue is that the built in max function has an incompatible signature so I made this one

public func max<T: Comparable>(_ values: T...) -> T {
    values.reduce(into: values.first!) { acc, el in acc = el > acc ? el : acc }
}

How would I correctly write this to work with variadic generics?


Additionally that enum is not itself generic, why is Swift complaining?

1 Like

You can do this with a local function:

    public init(_ alignment: Alignment = .centered, spacing: Int = 0, @DrawableBuilder drawables: () -> (repeat each Inner)) {
        let inner = drawables()

        self.width = 0
        func accum(_ width: Int) { self.width = max(self.width, width) }
        repeat accum((each inner).width)
    }

In the 6.0 developer snapshots, you can use pack iteration (Swift.org - Iterate Over Parameter Packs in Swift 6.0):

    public init(_ alignment: Alignment = .centered, spacing: Int = 0, @DrawableBuilder drawables: () -> (repeat each Inner)) {
        let inner = drawables()

        self.width = 0
        for i in repeat each inner {
          self.width = max(self.width, i.width)
        }
    }

What you can't do, because we don't have same-element requirements, is declare a variadic generic max() function (recall that Comparable needs both arguments to < to have the same type, and not just any two Comparable values):

func max<T: Comparable, each U>(_ first: T, _ rest: repeat each U)
    where repeat each U == T {...}

Precisely because the where clause requirement cannot be stated right now.

Types nested inside of generic contexts capture the generic parameters of the outer type. In this case, Alignment is really DrawableVStack<...>.Alignment, so it really is variadic generic. Since it doesn't have any payload cases, we could allow it in this case, but for now there is a blanket ban on parameter packs in enums because pattern matching and such isn't implemented yet.

4 Likes

Thank you :slight_smile:

While I generally like to minimize mutable state within functions I was now able to implement it with pack iteration as I am on the latest compiler snapshot, though it somewhat negatively impacted my compile times.

The enum also worked out in the end (I forgot about the nested generics :sweat_smile:) as I ended up making just the initializer generic rather than the entire struct, at the cost of immediately flattening the inner drawables into an image and allocating memory.

Yay did not take long for me to run into issues

warning: prohibited flag(s): -D_THREAD_SAFE
Building for production...
error: compile command failed due to signal 6 (use -v to see invocation)
SIL verification failed: Two variables with different type but same scope!: lhs == rhs || (lhs.isAddress() && lhs.getObjectType() == rhs) || (DebugVarTy.isAddress() && lhs == rhs.getObjectType())
Verifying instruction:
     %159 = open_pack_element %24 of <each D where repeat each D : Drawable> at <Pack{DrawableSlice<UnsafeTGAPointer>, DrawableSlice<UnsafeTGAPointer>, DrawableSlice<UnsafeTGAPointer>}>, shape $each D, uuid "E2759EB4-0E2D-11EF-B422-1EABE573A350" // users: %162, %176, %175, %174, %165, %164, %160

And this keeps going forever

I suppose it's very very unfinished still

Looks like a problem with emitting debug info. Do you have a reproducer? Does it work if you build without -g?

If you mean this then not really:

20:14 SwiftRogue > swift run -gnone
zsh: segmentation fault  swift run -gnone

Actually, a lot of flags seem to be crashing with a segfault. I'm on

Apple Swift version 6.0-dev (LLVM b66077aefd3be08, Swift 84d36181a762913)
Target: arm64-apple-macosx14.0

Oddly enough swift run works, but running from Xcode fails with a "nonzero exit code" which is not very helpful information.

I'm not sure how I can share a reproducer short of putting all my source code on GitHub; I'm mostly running into very specific issues that seem to be triggered just by moving the source code around.

Not sure if the issue is the same, but there's an open issue with the same output at SIL verification failed when using tuples containing parameter packs · Issue #73030 · apple/swift · GitHub.

2 Likes

Seems likely as my code is definitely using one of these tuples. Though it's not a change to the tuple that started crashing.

The code now looks like this:

public struct VStack: Drawable {
    public let image: Image
    public var width: Int { image.width }
    public var height: Int { image.height }
    
    public init<each D: Drawable>(
        alignment: Alignment = .centered,
        spacing: Int = 0,
        @DrawableBuilder drawables: () -> (repeat each D)
    ) {
        let inner = drawables()
        
        var width = 0
        var height = 0
        var count = 0
        for drawable in repeat each inner {
            count += 1
            width = width > drawable.width ? width : drawable.width
            height += drawable.height + spacing
        }
        
        var image = Image(width: width, height: height)
        
        var current = 0
        for drawable in repeat each inner {
            let offset = switch alignment {
                case .leading: 0
                case .trailing: width - drawable.width
                case .centered: (width - drawable.width) / 2
            }
            image.draw(drawable, x: offset, y: current)
            current += drawable.height + spacing
        }
        
        self.image = image
    }
    
    public subscript(x: Int, y: Int) -> Color { image[x, y] }
    
    public enum Alignment { case leading, trailing, centered }
}

the Image within the function to which I'm rendering into was now changed — Before methods like draw were provided by the type itself, but now they're derived from a protocol extension. The code started crashing after this change.


edit:

As a temporary workaround @_optimize(none) on the init avoids the crash.

1 Like

Is it expected for this code to leak memory? I ran Xcode leak detection on my program and that image is leaking memory constantly.

You might be hitting the issue that was fixed here: [6.0] Fix incorrect BitwiseCopyable conformance lookups by slavapestov · Pull Request #73462 · apple/swift · GitHub

1 Like