I suspect the nested option type in this particular implementation has to do with the step-by-step implementation in the guide, as you gradually add various features one at a time. As a result, you might end up with some features in your code that reflect less-general behaviors from earlier chapters, if you don't fully refactor them as you go along.
I don't think there are actually any cases in which you can usefully do something with the color but without the ray (the reason that the ray output is optional is that the ray might be absorbed rather than reflected, but in this case I think its color becomes meaningless, or becomes the equivalent of (0.0, 0.0, 0.0)). However, someone implementing this based on the tutorial might not think of it that way, because the different features in question were added separately at different times.
In the original C++ example in the tutorial, there is a boolean return value indicating whether there is an output ray or not, which I think corresponds to the Some case for the option type here. As there is ultimately only one boolean, not two, and as material implementations are expected to set both the ray and the color when scattering occurs, I think it's correct that you could combine the vector and color return values into a single struct wrapped in a single option type, at least with the implementation strategy that the tutorial is suggesting.
I worked through about 90% of the guide in Python and then about 60% in Rust; this article definitely makes me want to pick it up again.
Edit:
> The `Some((None, Srgb))` case would be a non-reflective surface, it changes the color if hit by a ray but does not reflect the ray further.
While this feels like a plausible guess, I don't believe it actually aligns with the strategy suggested in the tutorial. See section 8:
Diffuse materials (non-specular reflection) are implemented by having them scatter incident light in a random direction, possibly with some attenuation and some change of color. The change of color, though, is only meaningful when rays are scattered. See also section 9.3
(talking about how you can either scatter every ray and attenuate its intensity, or scatter a fraction of rays with no attenuation but absorb some rays at random, with the same statistical result on the output)
pub trait Scatterable { fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Srgb)>; }
I don't know rust but familiar with the Option type. Why would you not create a type that composes a Ray and a Srgb value, something like this?
struct RayColour/RayWithSrgb/etc { Ray, Srgb }
Seems like it would be a nicer API.