Why can't Int.init() take an optional StringProtocol?

I think the current implementation is perfectly reasonable: just use Optional.flatMap(_:), like so:

let port = Environment.get("DATABASE_PORT").flatMap(Int.init) ?? 3306

Methods should not accept arguments that they cannot do anything with. Allowing that reduces the compiler’s ability to catch programming mistakes.

As for Int.init(_:), returning nil does not mean that an error occurred. It means that the initializer successfully converted the given String into its corresponding Int value, which is no Int at all.

1 Like

The exact same thing can be said about nil. nil is "no Int at all."

Optional<Int>.none is no Int at all. nil is just an absence of a value. While I conflated the two just now, they aren’t quite the same thing.

1 Like

A really quick test, compiling this with swiftc -emit-assembly -O a.swift.

fooA has 2 function calls
fooB has 4 function calls
fooC has 2 function calls (one of which calls another, but I think it should be inlineable)

Calling flatMap is expensive.

func fooA(_ inS: String) -> Int {
    return Int(inS) ?? 123
}

func fooB(_ inS: String?) -> Int {
    return inS.flatMap(Int.init) ?? 123
}

func fooC(_ inS: String?) -> Int {
    return Int(inS) ?? 123
}

extension Int {
    init?(_ inS: String?) {
        guard let s = inS else { return nil }
        self.init(s)
    }
}

and fooA() becomes:

_$s1a4fooAySiSSF:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	pushq	%r14
	pushq	%rbx
	.cfi_offset %rbx, -32
	.cfi_offset %r14, -24
	movq	%rsi, %rbx
	movq	%rdi, %r14
	movq	%rsi, %rdi
	callq	_swift_bridgeObjectRetain
	movl	$10, %edx
	movq	%r14, %rdi
	movq	%rbx, %rsi
	callq	_$ss17FixedWidthIntegerPsE_5radixxSgqd___SitcSyRd__lufCSi_SSTg5Tf4nnd_n
	testb	$1, %dl
	movl	$123, %ecx
	cmovneq	%rcx, %rax
	popq	%rbx
	popq	%r14
	popq	%rbp
	retq
	.cfi_endproc

fooB() becomes:

_$s1a4fooBySiSSSgF:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	pushq	%r15
	pushq	%r14
	pushq	%rbx
	pushq	%rax
	.cfi_offset %rbx, -40
	.cfi_offset %r14, -32
	.cfi_offset %r15, -24
	testq	%rsi, %rsi
	je	LBB2_2
	movq	%rsi, %rbx
	movq	%rdi, %r14
	movq	%rsi, %rdi
	callq	_swift_bridgeObjectRetain
	movq	%r14, %rdi
	movq	%rbx, %rsi
	callq	_$sSSSgWOy
	movl	$10, %edx
	movq	%r14, %rdi
	movq	%rbx, %rsi
	callq	_$ss17FixedWidthIntegerPsE_5radixxSgqd___SitcSyRd__lufCSi_SSTg5Tf4nnd_n
	movq	%rax, %r14
	movl	%edx, %r15d
	movq	%rbx, %rdi
	callq	_swift_bridgeObjectRelease
	testb	$1, %r15b
	je	LBB2_3
LBB2_2:
	movl	$123, %r14d
LBB2_3:
	movq	%r14, %rax
	addq	$8, %rsp
	popq	%rbx
	popq	%r14
	popq	%r15
	popq	%rbp
	retq
	.cfi_endproc

fooC() becomes:

_$s1a4fooCySiSSSgF:
	.cfi_startproc
	testq	%rsi, %rsi
	je	LBB3_2
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	pushq	%r14
	pushq	%rbx
	.cfi_offset %rbx, -32
	.cfi_offset %r14, -24
	movq	%rsi, %rbx
	movq	%rdi, %r14
	callq	_$sSSSgWOy
	movl	$10, %edx
	movq	%r14, %rdi
	movq	%rbx, %rsi
	callq	_$ss17FixedWidthIntegerPsE_5radixxSgqd___SitcSyRd__lufCSi_SSTg5Tf4nnd_n
	testb	$1, %dl
	popq	%rbx
	popq	%r14
	popq	%rbp
	je	LBB3_3
LBB3_2:
	movl	$123, %eax
LBB3_3:
	retq
	.cfi_endproc

_$sSSSgWOy:
	pushq	%rbp
	movq	%rsp, %rbp
	testq	%rsi, %rsi
	je	LBB7_1
	movq	%rsi, %rdi
	popq	%rbp
	jmp	_swift_bridgeObjectRetain
LBB7_1:
	popq	%rbp
	retq

It's an extra retain/release.

1 Like

At a quick glance, it looks like an extra arc. It's bug-report worthy if the optimizer couldn't optimize out arc calls in reasonable places. That said, I wouldn't let this level of optimization affect my coding style, unless it's in a tight loop, or have tighter-than-usual runtime constraints.

2 Likes