The question is the problem: how do you do zero-copy sharing of data contained within a string. You can’t unless you couple it with the length. In rust parlance a slice is a well defined type.
There are also other reasons why having the length embedded in a string (or a string slice) is a good thing. You might want your “str_contains” function to do something different with different sized inputs: doing some vectorised lookup might only be worth it at a certain point, or if the length of the needle is greater than the haystack itself then there isn’t much point doing anything.
NULL terminated strings are a huge mistake that brings in security issues, needless copying and inefficient code that might contain several redundant strlen calls at different levels.