It's unclear what the implementation function for rule_custom does, but it should generate actions that take foo.txt and bar.txt as inputs (respectively) and output something.
Something that looks the following, where [action 1] is generated by your first target definition, and [action 2] is generated by your second one.
foo.txt
|
v
[action 1]
|
v
foo.out bar.txt
\ /
\ /
v v
[action 2]
|
v
bar.out
The action graph (built by running rule implementation functions) is what Bazel uses to determine whether to rebuild artifacts or not. It uses input/output chains between actions to determine which need to run, and in which order. Besides, it an action is run, but generates the same output as before, the output is considered up-to-date and its downstream actions will not be re-run.
Also each implementation rule must return a DefaultInfo value which corresponds to the outputs to generate when you do something like `bazel build //src:foo` or `bazel build //src:bar`.