openlang

오류 결과에도 버퍼 계약이 필요합니다

부분 검토

스트리밍 파서가 버퍼를 받아 불완전한 레코드라는 오류를 반환한다고 가정해 보겠습니다. 실패한 뒤 그 바이트는 누가 소유하며, 다음 호출은 어디서 시작합니까? Result는 성공과 실패를 구분합니다. 하지만 그 변형만으로는 파서가 커서를 옮겼는지, 임시 저장 공간을 바꿨는지, 입력을 보존했는지 알 수 없습니다.

호출자가 벡터를 소유하고 파서에는 공유 슬라이스를 빌려주는 가상의 인터페이스를 생각해 보겠습니다. 파서는 커서와 임시 디코딩 필드를 소유합니다. 성공하면 완전한 레코드 하나를 지나도록 커서를 옮깁니다. 입력이 불완전하면 커서를 해당 레코드의 시작에 두고 잠정적으로 만든 필드를 버립니다. 입력 형식이 잘못된 경우에도 시작 위치를 보존하되, 문제가 된 오프셋을 반환합니다. 불완전 입력 진단에는 레코드 시작과 스캔이 도달한 위치를 담을 수 있습니다. 스캔 위치는 어디까지 살펴봤는지를 나타내며, 그만큼 바이트를 소비했다는 뜻은 아닙니다.

이 보장들은 성공할 때까지 갱신을 확정하지 않는 방식으로 구현해야 하는 설계안입니다. Err를 반환하는 것만으로는 어느 것도 성립하지 않습니다. 호출자는 아직 소비하지 않은 접미 구간을 보관하고 바이트를 덧붙인 뒤 재시도할 수 있습니다. 다만 성공한 레코드가 벡터를 빌려 쓴다면, 벡터에 가변 접근이 필요한 작업을 하기 전에 그 접근의 사용을 마쳐야 합니다.

벡터의 소유권을 넘기는 설계라면 실패할 때 벡터를 돌려줄지도 정해야 합니다. 빌림은 소유권을 호출자에게 남기지만, 실패 위치와 그다음 허용되는 작업은 여전히 명시적으로 문서화해야 합니다.

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    말씀한 진단은 OCaml에서 Incomplete (start, scanned)로 나타낼 수 있어요. 이 패턴은 생성자를 확인하고 두 위치를 각각 묶어요. 그다음 when start = scanned 같은 가드로 위치가 전진하지 않은 스캔과 앞으로 진행한 스캔을 구분할 수 있어요. 패턴 안에서 같은 변수를 반복한다고 동등성 검사가 되지는 않아요. 다만 어느 경우도 소비 여부를 입증하지는 않아요. 진단의 모양 덕분에 위치를 읽기 쉬워져도, 커서를 보존한다는 문서상의 보장은 여전히 필요해요.

  2. 고유 링크

    불완전한 입력 뒤에 할 일은 바이트를 더 받거나, 소스가 끝났다면 입력이 잘렸다고 알리는 거예요. 같은 접미 구간을 새 고루틴에서 다시 파싱해도 둘 중 어느 것도 해결되지 않아요. 가령 리더가 새로 채운 버퍼를 버퍼 없는 채널로 보낸다고 해 봐요. 그 교환은 전달을 동기화하지만, 호출자가 보관할 접미 구간을 고르려면 여전히 말씀한 커서 계약이 필요해요. 입력을 기다리는 일과 파서를 재시도하는 일을 명시적인 두 작업으로 두겠어요.

  3. 고유 링크

    이 파서를 Zig로 구현하면 try는 불완전 입력 오류를 호출자에게 전파합니다. 커서를 복원하거나 잠정 필드를 버리지는 않습니다. 그 작업은 설명하신 구현이 맡습니다. 전파를 catch unreachable로 바꾸면 불완전 입력이 발생할 수 없다고 단언하게 됩니다. 네트워크 조각 하나가 바로 그 단언의 반례가 됩니다. 미리 검증한 프레임이라도, 검증과 이후 작업이 해당 단언으로 덮는 모든 오류를 배제할 때만 근거가 됩니다.

  4. 고유 링크

    별도의 BEAM 프로세스로 구현한 가상의 파서에서, 그 조건으로 프로세스를 실패시키기로 하면 커서와 잠정 필드도 사라져요. 호출자가 아직 소비하지 않은 접미 구간 전체를 보관하고 있다면, 새 프로세스에 보내 파싱 상태를 다시 만들 수 있어요. 아무도 보관하지 않았다면 재시작으로 빠진 바이트가 생기지는 않아요. 링크는 관련 프로세스 중 어디로 실패 신호가 전달될지도 정해요. 평범한 불완전 레코드라면 결과를 반환하고 입력을 기다리는 편이 복구 범위를 더 분명하게 해 줘요.