qsharp-compiler: Default values for qubits and callables in uninitialized arrays cause runtime errors
Describe the bug
Q# array initializers allow creating an array with default values for the given type. For callable and qubit types, the default value is an invalid callable and an invalid qubit.
To Reproduce
let f = (new (Int -> Int)[1])[0];
let _ = f(7);
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. —> Example.Main on C:\Users\samarsha\Desktop\QSharpProject\Program.qs:line 8
let q = (new Qubit[1])[0];
X(q);
Unhandled exception. System.ArgumentNullException: Trying to perform a primitive operation on a null Qubit (Parameter ‘q1’) —> Microsoft.Quantum.Intrinsic.X on :line 0 at Example.Main on C:\Users\samarsha\Desktop\QSharpProject\Program.qs:line 8
Expected behavior
There are no reasonable default values for qubits and callables, so Q# should make it harder or impossible to create them. In general, not every type will have a reasonable default, so there should be some way of requiring that a default exists before creating an array. One way to do this is make array constructors explicitly take in a value with which to initialize each cell.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 26 (26 by maintainers)
Yup.
Dropping
Defaultwould be a lovely thing to do. I think everything is simple if we drop it andnewat the same time.ConstantArrayalready exists, so the change to make it intrinsic can be done without affecting the Q# API.EmptyArrayshould complement it nicely. We couldn’t writeEmptyArrayin pure Q# usingConstantArray, though, withoutDefault<'T>, such that it would need to be intrinsic as well.Sounds like deprecate-and-drop, so that we can close the loophole where
Qubitcan be effectivelynull? If we want nullability, explicit DUs and type-parameterized UDTs are the way to go, I think:I agree, down with
new! I really like @cgranade’sEmptyArraysuggestion.Actually, I kind of like that…
Odd idea: remove the
newkeyword, and makeMicrosoft.Quantum.Arrays.ConstantArrayintrinsic.I think modifying the “new” operator to take an initial value would be a good thing, and actually useful – it makes it easier to create, say, an array of
PailiZs instead of an array ofPauliIs. That way, we don’t have to require a default value for every type, should we ever want to have a type with no values… This would be a breaking change, though.Yes - the implementation of
Defaultis how I discovered this bug. 😃In some contexts, yes, but I’m not sure that a universal default can be applied to a type without knowing that context. Array initializers think that the default
Intis0, butIndexOfwould say that the defaultIntis-1. In other cases, there is no reasonable default and you should useNonefromOption<'T>instead. I feel like default-ness depends too much on context to be a universal function.Thanks for the great examples @swernli! It’s good to show that null is so dangerous because it’s useful for control flow, people will be tempted to use it, and it can spread throughout a code base and libraries, encouraging more people to use null or defensively check for null.
We should fix this and provide the type-safe alternative: an option type (#406).
It turns out, null
Resultvariables allow for some very odd uses. They work in comparisons, but are neitherOnenorZero, effectively makingResulta ternary value instead of binary:That code will output “Two?!?” which is both hilarious and ripe for abuse.
Number 2 is especially dangerous, as
Defaultallows you to create null qubits or null results pretty trivially:This code will output
Values: [] , [], showing that both the variables are null.